@kapeta/local-cluster-service 0.12.0 → 0.13.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 +2 -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 +22 -1
- package/dist/cjs/src/containerManager.d.ts +1 -1
- package/dist/cjs/src/containerManager.js +132 -122
- package/dist/cjs/src/instanceManager.d.ts +4 -3
- package/dist/cjs/src/instanceManager.js +87 -60
- package/dist/cjs/src/instances/routes.js +21 -11
- package/dist/cjs/src/operatorManager.d.ts +5 -3
- package/dist/cjs/src/operatorManager.js +34 -22
- 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 +163 -0
- package/dist/cjs/src/tasks/routes.d.ts +3 -0
- package/dist/cjs/src/tasks/routes.js +35 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +0 -1
- package/dist/esm/index.js +2 -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 +22 -1
- package/dist/esm/src/containerManager.d.ts +1 -1
- package/dist/esm/src/containerManager.js +132 -122
- package/dist/esm/src/instanceManager.d.ts +4 -3
- package/dist/esm/src/instanceManager.js +87 -60
- package/dist/esm/src/instances/routes.js +21 -11
- package/dist/esm/src/operatorManager.d.ts +5 -3
- package/dist/esm/src/operatorManager.js +34 -22
- 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 +159 -0
- package/dist/esm/src/tasks/routes.d.ts +3 -0
- package/dist/esm/src/tasks/routes.js +30 -0
- package/dist/esm/src/utils/BlockInstanceRunner.js +0 -1
- package/index.ts +2 -0
- package/package.json +1 -1
- package/src/assetManager.ts +28 -4
- package/src/assets/routes.ts +23 -1
- package/src/containerManager.ts +153 -142
- package/src/instanceManager.ts +116 -70
- package/src/instances/routes.ts +20 -12
- package/src/operatorManager.ts +46 -26
- package/src/providerManager.ts +1 -1
- package/src/repositoryManager.ts +65 -63
- package/src/socketManager.ts +1 -1
- package/src/taskManager.ts +225 -0
- package/src/tasks/routes.ts +38 -0
- package/src/utils/BlockInstanceRunner.ts +0 -4
@@ -13,6 +13,7 @@ import { getBlockInstanceContainerName, normalizeKapetaUri } from './utils/utils
|
|
13
13
|
import { KIND_OPERATOR, operatorManager } from './operatorManager';
|
14
14
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
15
15
|
import { definitionsManager } from './definitionsManager';
|
16
|
+
import { Task, taskManager } from './taskManager';
|
16
17
|
const CHECK_INTERVAL = 5000;
|
17
18
|
const DEFAULT_HEALTH_PORT_TYPE = 'rest';
|
18
19
|
const EVENT_STATUS_CHANGED = 'status-changed';
|
@@ -49,7 +50,13 @@ export class InstanceManager {
|
|
49
50
|
return [];
|
50
51
|
}
|
51
52
|
systemId = normalizeKapetaUri(systemId);
|
52
|
-
|
53
|
+
const planInfo = definitionsManager.getDefinition(systemId);
|
54
|
+
if (!planInfo) {
|
55
|
+
return [];
|
56
|
+
}
|
57
|
+
const plan = planInfo.definition;
|
58
|
+
const instanceIds = plan.spec.blocks.map((block) => block.id);
|
59
|
+
return this._instances.filter((instance) => instance.systemId === systemId && instanceIds.includes(instance.instanceId));
|
53
60
|
}
|
54
61
|
getInstance(systemId, instanceId) {
|
55
62
|
systemId = normalizeKapetaUri(systemId);
|
@@ -205,27 +212,37 @@ export class InstanceManager {
|
|
205
212
|
systemId = normalizeKapetaUri(systemId);
|
206
213
|
const plan = await assetManager.getPlan(systemId, true);
|
207
214
|
if (!plan) {
|
208
|
-
throw new Error(
|
215
|
+
throw new Error(`Plan not found: ${systemId}`);
|
209
216
|
}
|
210
217
|
if (!plan.spec.blocks) {
|
211
|
-
|
212
|
-
return [];
|
218
|
+
throw new Error(`No blocks found in plan: ${systemId}`);
|
213
219
|
}
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
220
|
+
return taskManager.add(`plan:start:${systemId}`, async () => {
|
221
|
+
let promises = [];
|
222
|
+
let errors = [];
|
223
|
+
for (let blockInstance of Object.values(plan.spec.blocks)) {
|
224
|
+
try {
|
225
|
+
promises.push(this.start(systemId, blockInstance.id).then((taskOrInstance) => {
|
226
|
+
if (taskOrInstance instanceof Task) {
|
227
|
+
return taskOrInstance.wait();
|
228
|
+
}
|
229
|
+
return taskOrInstance;
|
230
|
+
}));
|
231
|
+
}
|
232
|
+
catch (e) {
|
233
|
+
errors.push(e);
|
234
|
+
}
|
219
235
|
}
|
220
|
-
|
221
|
-
|
236
|
+
const settled = await Promise.allSettled(promises);
|
237
|
+
if (errors.length > 0) {
|
238
|
+
throw errors[0];
|
222
239
|
}
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
240
|
+
return settled
|
241
|
+
.map((p) => (p.status === 'fulfilled' ? p.value : null))
|
242
|
+
.filter((p) => !!p);
|
243
|
+
}, {
|
244
|
+
name: `Starting plan ${systemId}`,
|
245
|
+
});
|
229
246
|
}
|
230
247
|
async stop(systemId, instanceId) {
|
231
248
|
return this.stopInner(systemId, instanceId, true);
|
@@ -282,10 +299,14 @@ export class InstanceManager {
|
|
282
299
|
}
|
283
300
|
});
|
284
301
|
}
|
285
|
-
|
302
|
+
stopAllForPlan(systemId) {
|
286
303
|
systemId = normalizeKapetaUri(systemId);
|
287
304
|
const instancesForPlan = this._instances.filter((instance) => instance.systemId === systemId);
|
288
|
-
return
|
305
|
+
return taskManager.add(`plan:stop:${systemId}`, async () => {
|
306
|
+
return this.stopInstances(instancesForPlan);
|
307
|
+
}, {
|
308
|
+
name: `Stopping plan ${systemId}`,
|
309
|
+
});
|
289
310
|
}
|
290
311
|
async start(systemId, instanceId) {
|
291
312
|
return this.exclusive(systemId, instanceId, async () => {
|
@@ -361,47 +382,53 @@ export class InstanceManager {
|
|
361
382
|
}
|
362
383
|
}
|
363
384
|
const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
|
364
|
-
const
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
385
|
+
const task = taskManager.add(`instance:start:${systemId}:${instanceId}`, async () => {
|
386
|
+
const runner = new BlockInstanceRunner(systemId);
|
387
|
+
const startTime = Date.now();
|
388
|
+
try {
|
389
|
+
const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
|
390
|
+
instance.status = InstanceStatus.READY;
|
391
|
+
return this.saveInternalInstance({
|
392
|
+
...instance,
|
393
|
+
type: processInfo.type,
|
394
|
+
pid: processInfo.pid ?? -1,
|
395
|
+
health: null,
|
396
|
+
portType: processInfo.portType,
|
397
|
+
status: InstanceStatus.READY,
|
398
|
+
});
|
399
|
+
}
|
400
|
+
catch (e) {
|
401
|
+
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e.message);
|
402
|
+
const logs = [
|
403
|
+
{
|
404
|
+
source: 'stdout',
|
405
|
+
level: 'ERROR',
|
406
|
+
message: e.message,
|
407
|
+
time: Date.now(),
|
408
|
+
},
|
409
|
+
];
|
410
|
+
const out = await this.saveInternalInstance({
|
411
|
+
...instance,
|
412
|
+
type: InstanceType.UNKNOWN,
|
413
|
+
pid: null,
|
414
|
+
health: null,
|
415
|
+
portType: DEFAULT_HEALTH_PORT_TYPE,
|
416
|
+
status: InstanceStatus.FAILED,
|
417
|
+
errorMessage: e.message ?? 'Failed to start - Check logs for details.',
|
418
|
+
});
|
419
|
+
this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
|
420
|
+
this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
|
421
|
+
error: `Failed to start instance: ${e.message}`,
|
422
|
+
status: EVENT_INSTANCE_EXITED,
|
423
|
+
instanceId: blockInstance.id,
|
424
|
+
});
|
425
|
+
return out;
|
426
|
+
}
|
427
|
+
}, {
|
428
|
+
name: `Starting instance: ${instance.name}`,
|
429
|
+
systemId,
|
430
|
+
});
|
431
|
+
return task;
|
405
432
|
});
|
406
433
|
}
|
407
434
|
/**
|
@@ -5,6 +5,7 @@ import { corsHandler } from '../middleware/cors';
|
|
5
5
|
import { kapetaHeaders } from '../middleware/kapeta';
|
6
6
|
import { stringBody } from '../middleware/stringBody';
|
7
7
|
import { DesiredInstanceStatus, InstanceOwner, InstanceType } from '../types';
|
8
|
+
import { Task } from '../taskManager';
|
8
9
|
const router = Router();
|
9
10
|
router.use('/', corsHandler);
|
10
11
|
router.use('/', kapetaHeaders);
|
@@ -24,33 +25,40 @@ router.get('/:systemId/instances', (req, res) => {
|
|
24
25
|
* Start all instances in a plan
|
25
26
|
*/
|
26
27
|
router.post('/:systemId/start', async (req, res) => {
|
27
|
-
const
|
28
|
+
const task = await instanceManager.startAllForPlan(req.params.systemId);
|
28
29
|
res.status(202).send({
|
29
30
|
ok: true,
|
30
|
-
|
31
|
-
return { pid: p.pid, type: p.type };
|
32
|
-
}),
|
31
|
+
taskId: task.id,
|
33
32
|
});
|
34
33
|
});
|
35
34
|
/**
|
36
35
|
* Stop all instances in plan
|
37
36
|
*/
|
38
37
|
router.post('/:systemId/stop', async (req, res) => {
|
39
|
-
|
38
|
+
const task = instanceManager.stopAllForPlan(req.params.systemId);
|
40
39
|
res.status(202).send({
|
41
40
|
ok: true,
|
41
|
+
taskId: task.id,
|
42
42
|
});
|
43
43
|
});
|
44
44
|
/**
|
45
45
|
* Start single instance in a plan
|
46
46
|
*/
|
47
47
|
router.post('/:systemId/:instanceId/start', async (req, res) => {
|
48
|
-
const
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
const result = await instanceManager.start(req.params.systemId, req.params.instanceId);
|
49
|
+
if (result instanceof Task) {
|
50
|
+
res.status(202).send({
|
51
|
+
ok: true,
|
52
|
+
taskId: result.id,
|
53
|
+
});
|
54
|
+
}
|
55
|
+
else {
|
56
|
+
res.status(202).send({
|
57
|
+
ok: true,
|
58
|
+
pid: result.pid,
|
59
|
+
type: result.type,
|
60
|
+
});
|
61
|
+
}
|
54
62
|
});
|
55
63
|
/**
|
56
64
|
* Stop single instance in a plan
|
@@ -117,8 +125,10 @@ router.put('/', async (req, res) => {
|
|
117
125
|
const oldInstance = instanceManager.getInstance(req.kapeta.systemId, req.kapeta.instanceId);
|
118
126
|
if (oldInstance) {
|
119
127
|
instance.pid = oldInstance.pid;
|
128
|
+
instance.desiredStatus = oldInstance.desiredStatus;
|
120
129
|
}
|
121
130
|
instance.type = InstanceType.DOCKER;
|
131
|
+
instance.owner = InstanceOwner.INTERNAL;
|
122
132
|
}
|
123
133
|
else {
|
124
134
|
// Coming from user starting the instance outside of kapeta
|
@@ -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 {
|
@@ -9,17 +9,21 @@ import { definitionsManager } from './definitionsManager';
|
|
9
9
|
import { getBindHost, normalizeKapetaUri } from './utils/utils';
|
10
10
|
import _ from 'lodash';
|
11
11
|
import AsyncLock from 'async-lock';
|
12
|
+
import { taskManager } from './taskManager';
|
12
13
|
export const KIND_OPERATOR = 'core/resource-type-operator';
|
13
14
|
class Operator {
|
14
15
|
_data;
|
15
16
|
constructor(data) {
|
16
17
|
this._data = data;
|
17
18
|
}
|
18
|
-
|
19
|
+
getLocalData() {
|
20
|
+
return this._data.definition.spec.local;
|
21
|
+
}
|
22
|
+
getDefinitionInfo() {
|
19
23
|
return this._data;
|
20
24
|
}
|
21
25
|
getCredentials() {
|
22
|
-
return this._data.credentials;
|
26
|
+
return this._data.definition.spec.local.credentials;
|
23
27
|
}
|
24
28
|
}
|
25
29
|
class OperatorManager {
|
@@ -52,7 +56,7 @@ class OperatorManager {
|
|
52
56
|
if (!operator.definition.spec || !operator.definition.spec.local) {
|
53
57
|
throw new Error(`Operator missing local definition: ${resourceType}:${version}`);
|
54
58
|
}
|
55
|
-
return new Operator(operator
|
59
|
+
return new Operator(operator);
|
56
60
|
}
|
57
61
|
/**
|
58
62
|
* Get information about a specific consumed resource
|
@@ -113,7 +117,7 @@ class OperatorManager {
|
|
113
117
|
const key = `${systemId}#${resourceType}:${version}`;
|
114
118
|
return await this.operatorLock.acquire(key, async () => {
|
115
119
|
const operator = this.getOperator(resourceType, version);
|
116
|
-
const operatorData = operator.
|
120
|
+
const operatorData = operator.getLocalData();
|
117
121
|
const portTypes = Object.keys(operatorData.ports);
|
118
122
|
portTypes.sort();
|
119
123
|
const ports = {};
|
@@ -141,6 +145,7 @@ class OperatorManager {
|
|
141
145
|
const Labels = {
|
142
146
|
kapeta: 'true',
|
143
147
|
};
|
148
|
+
const operatorMetadata = operator.getDefinitionInfo().definition.metadata;
|
144
149
|
const bindHost = getBindHost();
|
145
150
|
const ExposedPorts = {};
|
146
151
|
_.forEach(ports, (portInfo, containerPort) => {
|
@@ -157,25 +162,32 @@ class OperatorManager {
|
|
157
162
|
_.forEach(operatorData.env, (value, name) => {
|
158
163
|
Env.push(name + '=' + value);
|
159
164
|
});
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
165
|
+
const task = taskManager.add(`operator:ensure:${key}`, async () => {
|
166
|
+
let HealthCheck = undefined;
|
167
|
+
if (operatorData.health) {
|
168
|
+
HealthCheck = containerManager.toDockerHealth(operatorData.health);
|
169
|
+
}
|
170
|
+
const container = await containerManager.ensureContainer({
|
171
|
+
name: containerName,
|
172
|
+
Image: operatorData.image,
|
173
|
+
Hostname: containerName + '.kapeta',
|
174
|
+
Labels,
|
175
|
+
Cmd: operatorData.cmd,
|
176
|
+
ExposedPorts,
|
177
|
+
Env,
|
178
|
+
HealthCheck,
|
179
|
+
HostConfig: {
|
180
|
+
PortBindings,
|
181
|
+
Mounts,
|
182
|
+
},
|
183
|
+
});
|
184
|
+
await containerManager.waitForReady(container);
|
185
|
+
return new ContainerInfo(container);
|
186
|
+
}, {
|
187
|
+
name: `Ensuring ${operatorMetadata.title ?? operatorMetadata.name}`,
|
188
|
+
systemId,
|
177
189
|
});
|
178
|
-
return
|
190
|
+
return task.wait();
|
179
191
|
});
|
180
192
|
}
|
181
193
|
}
|
@@ -16,7 +16,7 @@ class ProviderManager {
|
|
16
16
|
if (this._webAssetCache[id] && (await FSExtra.pathExists(this._webAssetCache[id]))) {
|
17
17
|
return FSExtra.readFile(this._webAssetCache[id], 'utf8');
|
18
18
|
}
|
19
|
-
await repositoryManager.ensureAsset(handle, name, version);
|
19
|
+
await repositoryManager.ensureAsset(handle, name, version, true);
|
20
20
|
const installedProvider = this.getWebProviders().find((providerDefinition) => {
|
21
21
|
return providerDefinition.definition.metadata.name === fullName && providerDefinition.version === version;
|
22
22
|
});
|
@@ -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 {};
|
@@ -9,20 +9,20 @@ import { socketManager } from './socketManager';
|
|
9
9
|
import { progressListener } from './progressListener';
|
10
10
|
import { Actions, Config, RegistryService } from '@kapeta/nodejs-registry-utils';
|
11
11
|
import { definitionsManager } from './definitionsManager';
|
12
|
+
import { taskManager } from './taskManager';
|
13
|
+
import { normalizeKapetaUri } from './utils/utils';
|
14
|
+
import { assetManager } from './assetManager';
|
12
15
|
const INSTALL_ATTEMPTED = {};
|
13
16
|
class RepositoryManager {
|
14
17
|
changeEventsEnabled;
|
15
18
|
_registryService;
|
16
19
|
_cache;
|
17
20
|
watcher;
|
18
|
-
_installQueue;
|
19
|
-
_processing = false;
|
20
21
|
constructor() {
|
21
22
|
this.changeEventsEnabled = true;
|
22
23
|
this.listenForChanges();
|
23
24
|
this._registryService = new RegistryService(Config.data.registry.url);
|
24
25
|
this._cache = {};
|
25
|
-
this._installQueue = [];
|
26
26
|
}
|
27
27
|
setChangeEventsEnabled(enabled) {
|
28
28
|
this.changeEventsEnabled = enabled;
|
@@ -103,74 +103,54 @@ class RepositoryManager {
|
|
103
103
|
}
|
104
104
|
async _install(refs) {
|
105
105
|
//We make sure to only install one asset at a time - otherwise unexpected things might happen
|
106
|
-
const
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
const filteredRefs = normalizedRefs
|
111
|
-
.filter((ref) => !INSTALL_ATTEMPTED[ref])
|
112
|
-
.filter((ref) => !definitionsManager.exists(ref));
|
113
|
-
if (filteredRefs.length > 0) {
|
114
|
-
console.log(`Auto-installing dependencies: ${filteredRefs.join(', ')}`);
|
115
|
-
filteredRefs.forEach((ref) => (INSTALL_ATTEMPTED[ref] = true));
|
116
|
-
//Auto-install missing asset
|
117
|
-
try {
|
118
|
-
//We change to a temp dir to avoid issues with the current working directory
|
119
|
-
process.chdir(os.tmpdir());
|
120
|
-
//Disable change events while installing
|
121
|
-
this.setChangeEventsEnabled(false);
|
122
|
-
socketManager.emit(`install`, 'install:action', {
|
123
|
-
type: 'start',
|
124
|
-
refs,
|
125
|
-
});
|
126
|
-
await Actions.install(progressListener, normalizedRefs, {});
|
127
|
-
socketManager.emit(`install`, 'install:action', {
|
128
|
-
type: 'done',
|
129
|
-
refs,
|
130
|
-
});
|
131
|
-
}
|
132
|
-
catch (e) {
|
133
|
-
socketManager.emit(`install`, 'install:action', {
|
134
|
-
type: 'failed',
|
135
|
-
refs,
|
136
|
-
error: e.message,
|
137
|
-
});
|
138
|
-
}
|
139
|
-
finally {
|
140
|
-
this.setChangeEventsEnabled(true);
|
141
|
-
}
|
142
|
-
}
|
143
|
-
resolve();
|
106
|
+
const createInstaller = (ref) => {
|
107
|
+
return async () => {
|
108
|
+
if (INSTALL_ATTEMPTED[ref]) {
|
109
|
+
return;
|
144
110
|
}
|
145
|
-
|
146
|
-
|
111
|
+
if (definitionsManager.exists(ref)) {
|
112
|
+
return;
|
147
113
|
}
|
148
|
-
|
149
|
-
|
114
|
+
console.log(`Installing asset: ${ref}`);
|
115
|
+
INSTALL_ATTEMPTED[ref] = true;
|
116
|
+
//Auto-install missing asset
|
117
|
+
try {
|
118
|
+
//We change to a temp dir to avoid issues with the current working directory
|
119
|
+
process.chdir(os.tmpdir());
|
120
|
+
//Disable change events while installing
|
121
|
+
this.setChangeEventsEnabled(false);
|
122
|
+
await Actions.install(progressListener, [ref], {});
|
150
123
|
}
|
151
|
-
|
152
|
-
|
153
|
-
this._processNext().catch((e) => console.error(e));
|
154
|
-
return out;
|
155
|
-
}
|
156
|
-
async _processNext() {
|
157
|
-
if (this._processing) {
|
158
|
-
return;
|
159
|
-
}
|
160
|
-
this._processing = true;
|
161
|
-
try {
|
162
|
-
while (this._installQueue.length > 0) {
|
163
|
-
const item = this._installQueue.shift();
|
164
|
-
if (item) {
|
165
|
-
await item();
|
124
|
+
finally {
|
125
|
+
this.setChangeEventsEnabled(true);
|
166
126
|
}
|
127
|
+
definitionsManager.clearCache();
|
128
|
+
assetManager.clearCache();
|
129
|
+
console.log(`Asset installed: ${ref}`);
|
130
|
+
};
|
131
|
+
};
|
132
|
+
const tasks = [];
|
133
|
+
while (refs.length > 0) {
|
134
|
+
let ref = refs.shift();
|
135
|
+
if (!ref) {
|
136
|
+
continue;
|
167
137
|
}
|
138
|
+
ref = normalizeKapetaUri(ref);
|
139
|
+
if (INSTALL_ATTEMPTED[ref]) {
|
140
|
+
continue;
|
141
|
+
}
|
142
|
+
if (definitionsManager.exists(ref)) {
|
143
|
+
continue;
|
144
|
+
}
|
145
|
+
const task = taskManager.add(`asset:install:${ref}`, createInstaller(ref), {
|
146
|
+
name: `Installing ${ref}`,
|
147
|
+
group: 'asset:install:',
|
148
|
+
});
|
149
|
+
tasks.push(task);
|
168
150
|
}
|
169
|
-
|
170
|
-
this._processing = false;
|
171
|
-
}
|
151
|
+
return tasks;
|
172
152
|
}
|
173
|
-
async ensureAsset(handle, name, version) {
|
153
|
+
async ensureAsset(handle, name, version, wait = true) {
|
174
154
|
const fullName = `${handle}/${name}`;
|
175
155
|
const ref = `${fullName}:${version}`;
|
176
156
|
if (version === 'local') {
|
@@ -201,16 +181,21 @@ class RepositoryManager {
|
|
201
181
|
throw e;
|
202
182
|
}
|
203
183
|
this._cache[ref] = true;
|
184
|
+
let tasks = undefined;
|
204
185
|
if (!installedAsset) {
|
205
|
-
await this._install([ref]);
|
186
|
+
tasks = await this._install([ref]);
|
206
187
|
}
|
207
188
|
else {
|
208
189
|
//Ensure dependencies are installed
|
209
190
|
const refs = assetVersion.dependencies.map((dep) => dep.name);
|
210
191
|
if (refs.length > 0) {
|
211
|
-
await this._install(refs);
|
192
|
+
tasks = await this._install(refs);
|
212
193
|
}
|
213
194
|
}
|
195
|
+
if (tasks && wait) {
|
196
|
+
await Promise.all(tasks.map((t) => t.future.promise));
|
197
|
+
}
|
198
|
+
return tasks;
|
214
199
|
}
|
215
200
|
}
|
216
201
|
export const repositoryManager = new RepositoryManager();
|
@@ -24,7 +24,7 @@ export class SocketManager {
|
|
24
24
|
this.io.to(context).emit(type, { context, payload });
|
25
25
|
}
|
26
26
|
emitGlobal(type, payload) {
|
27
|
-
this.io.emit(type,
|
27
|
+
this.io.emit(type, payload);
|
28
28
|
}
|
29
29
|
_bindIO() {
|
30
30
|
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 {};
|