@kapeta/local-cluster-service 0.12.1 → 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 +7 -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.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 +163 -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 +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.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 +159 -0
- package/dist/esm/src/tasks/routes.d.ts +3 -0
- package/dist/esm/src/tasks/routes.js +30 -0
- 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 +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 +225 -0
- package/src/tasks/routes.ts +38 -0
- package/src/utils/BlockInstanceRunner.ts +0 -2
package/src/assets/routes.ts
CHANGED
@@ -44,8 +44,15 @@ router.get('/read', async (req: Request, res: Response) => {
|
|
44
44
|
return;
|
45
45
|
}
|
46
46
|
|
47
|
+
const ensure = req.query.ensure !== 'false';
|
48
|
+
|
47
49
|
try {
|
48
|
-
|
50
|
+
const asset = await assetManager.getAsset(req.query.ref as string, true, ensure);
|
51
|
+
if (asset) {
|
52
|
+
res.send(asset);
|
53
|
+
} else {
|
54
|
+
res.status(404).send({ error: 'Asset not found' });
|
55
|
+
}
|
49
56
|
} catch (err: any) {
|
50
57
|
res.status(400).send({ error: err.message });
|
51
58
|
}
|
@@ -129,4 +136,19 @@ router.put('/import', async (req: Request, res: Response) => {
|
|
129
136
|
}
|
130
137
|
});
|
131
138
|
|
139
|
+
router.put('/install', async (req: Request, res: Response) => {
|
140
|
+
if (!req.query.ref) {
|
141
|
+
res.status(400).send({ error: 'Query parameter "ref" is missing' });
|
142
|
+
return;
|
143
|
+
}
|
144
|
+
|
145
|
+
try {
|
146
|
+
const tasks = await assetManager.installAsset(req.query.ref as string);
|
147
|
+
const taskIds = tasks?.map((t) => t.id) ?? [];
|
148
|
+
res.status(200).send(taskIds);
|
149
|
+
} catch (err: any) {
|
150
|
+
res.status(400).send({ error: err.message });
|
151
|
+
}
|
152
|
+
});
|
153
|
+
|
132
154
|
export default router;
|
package/src/containerManager.ts
CHANGED
@@ -15,6 +15,7 @@ import { socketManager } from './socketManager';
|
|
15
15
|
import { handlers as ArtifactHandlers } from '@kapeta/nodejs-registry-utils';
|
16
16
|
import { progressListener } from './progressListener';
|
17
17
|
import { KapetaAPI } from '@kapeta/nodejs-api-client';
|
18
|
+
import { taskManager, Task } from './taskManager';
|
18
19
|
|
19
20
|
const EVENT_IMAGE_PULL = 'docker-image-pull';
|
20
21
|
|
@@ -67,7 +68,6 @@ const NANO_SECOND = 1000000;
|
|
67
68
|
const HEALTH_CHECK_INTERVAL = 3000;
|
68
69
|
const HEALTH_CHECK_MAX = 20;
|
69
70
|
|
70
|
-
|
71
71
|
export const HEALTH_CHECK_TIMEOUT = HEALTH_CHECK_INTERVAL * HEALTH_CHECK_MAX * 2;
|
72
72
|
|
73
73
|
const promisifyStream = (stream: ReadStream, handler: (d: string | Buffer) => void) =>
|
@@ -238,151 +238,176 @@ class ContainerManager {
|
|
238
238
|
return false;
|
239
239
|
}
|
240
240
|
|
241
|
-
|
242
|
-
|
241
|
+
let friendlyImageName = image;
|
242
|
+
const imageParts = imageName.split('/');
|
243
|
+
if (imageParts.length > 2) {
|
244
|
+
//Strip the registry to make the name shorter
|
245
|
+
friendlyImageName = `${imageParts.slice(1).join('/')}:${tag}`;
|
246
|
+
}
|
243
247
|
|
244
|
-
const
|
245
|
-
|
248
|
+
const taskName = `Pulling image ${friendlyImageName}`;
|
249
|
+
|
250
|
+
const processor = async (task: Task) => {
|
251
|
+
const timeStarted = Date.now();
|
252
|
+
const api = new KapetaAPI();
|
253
|
+
const accessToken = await api.getAccessToken();
|
254
|
+
|
255
|
+
const auth = image.startsWith('docker.kapeta.com/')
|
256
|
+
? {
|
257
|
+
username: 'kapeta',
|
258
|
+
password: accessToken,
|
259
|
+
serveraddress: 'docker.kapeta.com',
|
260
|
+
}
|
261
|
+
: {};
|
262
|
+
|
263
|
+
const stream = (await this.docker().image.create(auth, {
|
264
|
+
fromImage: imageName,
|
265
|
+
tag: tag,
|
266
|
+
})) as ReadStream;
|
267
|
+
|
268
|
+
const chunks: {
|
269
|
+
[p: string]: {
|
270
|
+
downloading: {
|
271
|
+
total: number;
|
272
|
+
current: number;
|
273
|
+
};
|
274
|
+
extracting: {
|
275
|
+
total: number;
|
276
|
+
current: number;
|
277
|
+
};
|
278
|
+
done: boolean;
|
279
|
+
};
|
280
|
+
} = {};
|
281
|
+
|
282
|
+
let lastEmitted = Date.now();
|
283
|
+
await promisifyStream(stream, (rawData) => {
|
284
|
+
const lines = rawData.toString().trim().split('\n');
|
285
|
+
lines.forEach((line) => {
|
286
|
+
const data = JSON.parse(line);
|
287
|
+
if (
|
288
|
+
![
|
289
|
+
'Waiting',
|
290
|
+
'Downloading',
|
291
|
+
'Extracting',
|
292
|
+
'Download complete',
|
293
|
+
'Pull complete',
|
294
|
+
'Already exists',
|
295
|
+
].includes(data.status)
|
296
|
+
) {
|
297
|
+
return;
|
298
|
+
}
|
246
299
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
300
|
+
if (!chunks[data.id]) {
|
301
|
+
chunks[data.id] = {
|
302
|
+
downloading: {
|
303
|
+
total: 0,
|
304
|
+
current: 0,
|
305
|
+
},
|
306
|
+
extracting: {
|
307
|
+
total: 0,
|
308
|
+
current: 0,
|
309
|
+
},
|
310
|
+
done: false,
|
311
|
+
};
|
312
|
+
}
|
254
313
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
314
|
+
const chunk = chunks[data.id];
|
315
|
+
|
316
|
+
switch (data.status) {
|
317
|
+
case 'Downloading':
|
318
|
+
chunk.downloading = data.progressDetail;
|
319
|
+
break;
|
320
|
+
case 'Extracting':
|
321
|
+
chunk.extracting = data.progressDetail;
|
322
|
+
break;
|
323
|
+
case 'Download complete':
|
324
|
+
chunk.downloading.current = chunks[data.id].downloading.total;
|
325
|
+
break;
|
326
|
+
case 'Pull complete':
|
327
|
+
chunk.extracting.current = chunks[data.id].extracting.total;
|
328
|
+
chunk.done = true;
|
329
|
+
break;
|
330
|
+
case 'Already exists':
|
331
|
+
// Force layer to be done
|
332
|
+
chunk.downloading.current = 1;
|
333
|
+
chunk.downloading.total = 1;
|
334
|
+
chunk.extracting.current = 1;
|
335
|
+
chunk.extracting.total = 1;
|
336
|
+
chunk.done = true;
|
337
|
+
break;
|
338
|
+
}
|
339
|
+
});
|
259
340
|
|
260
|
-
|
261
|
-
[p: string]: {
|
262
|
-
downloading: {
|
263
|
-
total: number;
|
264
|
-
current: number;
|
265
|
-
};
|
266
|
-
extracting: {
|
267
|
-
total: number;
|
268
|
-
current: number;
|
269
|
-
};
|
270
|
-
done: boolean;
|
271
|
-
};
|
272
|
-
} = {};
|
273
|
-
|
274
|
-
let lastEmitted = Date.now();
|
275
|
-
await promisifyStream(stream, (rawData) => {
|
276
|
-
const lines = rawData.toString().trim().split('\n');
|
277
|
-
lines.forEach((line) => {
|
278
|
-
const data = JSON.parse(line);
|
279
|
-
if (
|
280
|
-
![
|
281
|
-
'Waiting',
|
282
|
-
'Downloading',
|
283
|
-
'Extracting',
|
284
|
-
'Download complete',
|
285
|
-
'Pull complete',
|
286
|
-
'Already exists',
|
287
|
-
].includes(data.status)
|
288
|
-
) {
|
341
|
+
if (Date.now() - lastEmitted < 1000) {
|
289
342
|
return;
|
290
343
|
}
|
291
344
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
}
|
305
|
-
|
306
|
-
const chunk = chunks[data.id];
|
307
|
-
|
308
|
-
switch (data.status) {
|
309
|
-
case 'Downloading':
|
310
|
-
chunk.downloading = data.progressDetail;
|
311
|
-
break;
|
312
|
-
case 'Extracting':
|
313
|
-
chunk.extracting = data.progressDetail;
|
314
|
-
break;
|
315
|
-
case 'Download complete':
|
316
|
-
chunk.downloading.current = chunks[data.id].downloading.total;
|
317
|
-
break;
|
318
|
-
case 'Pull complete':
|
319
|
-
chunk.extracting.current = chunks[data.id].extracting.total;
|
320
|
-
chunk.done = true;
|
321
|
-
break;
|
322
|
-
case 'Already exists':
|
323
|
-
// Force layer to be done
|
324
|
-
chunk.downloading.current = 1;
|
325
|
-
chunk.downloading.total = 1;
|
326
|
-
chunk.extracting.current = 1;
|
327
|
-
chunk.extracting.total = 1;
|
328
|
-
chunk.done = true;
|
329
|
-
break;
|
330
|
-
}
|
331
|
-
});
|
345
|
+
const chunkList = Object.values(chunks);
|
346
|
+
let totals = {
|
347
|
+
downloading: {
|
348
|
+
total: 0,
|
349
|
+
current: 0,
|
350
|
+
},
|
351
|
+
extracting: {
|
352
|
+
total: 0,
|
353
|
+
current: 0,
|
354
|
+
},
|
355
|
+
total: chunkList.length,
|
356
|
+
done: 0,
|
357
|
+
};
|
332
358
|
|
333
|
-
|
334
|
-
|
335
|
-
|
359
|
+
chunkList.forEach((chunk) => {
|
360
|
+
if (chunk.downloading.current > 0) {
|
361
|
+
totals.downloading.current += chunk.downloading.current;
|
362
|
+
}
|
336
363
|
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
total: 0,
|
341
|
-
current: 0,
|
342
|
-
},
|
343
|
-
extracting: {
|
344
|
-
total: 0,
|
345
|
-
current: 0,
|
346
|
-
},
|
347
|
-
total: chunkList.length,
|
348
|
-
done: 0,
|
349
|
-
};
|
364
|
+
if (chunk.downloading.total > 0) {
|
365
|
+
totals.downloading.total += chunk.downloading.total;
|
366
|
+
}
|
350
367
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
}
|
368
|
+
if (chunk.extracting.current > 0) {
|
369
|
+
totals.extracting.current += chunk.extracting.current;
|
370
|
+
}
|
355
371
|
|
356
|
-
|
357
|
-
|
358
|
-
|
372
|
+
if (chunk.extracting.total > 0) {
|
373
|
+
totals.extracting.total += chunk.extracting.total;
|
374
|
+
}
|
359
375
|
|
360
|
-
|
361
|
-
|
362
|
-
|
376
|
+
if (chunk.done) {
|
377
|
+
totals.done++;
|
378
|
+
}
|
379
|
+
});
|
363
380
|
|
364
|
-
|
365
|
-
totals.extracting.total += chunk.extracting.total;
|
366
|
-
}
|
381
|
+
const progress = totals.total > 0 ? (totals.done / totals.total) * 100 : 0;
|
367
382
|
|
368
|
-
|
369
|
-
|
370
|
-
|
383
|
+
task.metadata = {
|
384
|
+
...task.metadata,
|
385
|
+
image,
|
386
|
+
progress,
|
387
|
+
status: totals,
|
388
|
+
timeTaken: Date.now() - timeStarted,
|
389
|
+
};
|
390
|
+
task.emitUpdate();
|
391
|
+
lastEmitted = Date.now();
|
392
|
+
//console.log('Pulling image %s: %s % [done: %s, total: %s]', image, Math.round(percent), totals.done, totals.total);
|
371
393
|
});
|
372
394
|
|
373
|
-
|
374
|
-
|
375
|
-
socketManager.emitGlobal(EVENT_IMAGE_PULL, {
|
395
|
+
task.metadata = {
|
396
|
+
...task.metadata,
|
376
397
|
image,
|
377
|
-
|
378
|
-
status: totals,
|
398
|
+
progress: 100,
|
379
399
|
timeTaken: Date.now() - timeStarted,
|
380
|
-
}
|
381
|
-
|
382
|
-
|
400
|
+
};
|
401
|
+
task.emitUpdate();
|
402
|
+
};
|
403
|
+
|
404
|
+
const task = taskManager.add(`docker:image:pull:${image}`, processor, {
|
405
|
+
name: taskName,
|
406
|
+
image,
|
407
|
+
progress: -1,
|
383
408
|
});
|
384
409
|
|
385
|
-
|
410
|
+
await task.wait();
|
386
411
|
|
387
412
|
return true;
|
388
413
|
}
|
package/src/instanceManager.ts
CHANGED
@@ -9,11 +9,12 @@ import { assetManager } from './assetManager';
|
|
9
9
|
import { containerManager, HEALTH_CHECK_TIMEOUT } from './containerManager';
|
10
10
|
import { configManager } from './configManager';
|
11
11
|
import { DesiredInstanceStatus, InstanceInfo, InstanceOwner, InstanceStatus, InstanceType, LogEntry } from './types';
|
12
|
-
import {BlockDefinitionSpec, BlockInstance, Plan} from '@kapeta/schemas';
|
12
|
+
import { BlockDefinitionSpec, BlockInstance, Plan } from '@kapeta/schemas';
|
13
13
|
import { getBlockInstanceContainerName, normalizeKapetaUri } from './utils/utils';
|
14
14
|
import { KIND_OPERATOR, operatorManager } from './operatorManager';
|
15
15
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
16
16
|
import { definitionsManager } from './definitionsManager';
|
17
|
+
import { Task, taskManager } from './taskManager';
|
17
18
|
|
18
19
|
const CHECK_INTERVAL = 5000;
|
19
20
|
const DEFAULT_HEALTH_PORT_TYPE = 'rest';
|
@@ -74,7 +75,9 @@ export class InstanceManager {
|
|
74
75
|
|
75
76
|
const instanceIds = plan.spec.blocks.map((block) => block.id);
|
76
77
|
|
77
|
-
return this._instances.filter(
|
78
|
+
return this._instances.filter(
|
79
|
+
(instance) => instance.systemId === systemId && instanceIds.includes(instance.instanceId)
|
80
|
+
);
|
78
81
|
}
|
79
82
|
|
80
83
|
public getInstance(systemId: string, instanceId: string) {
|
@@ -271,35 +274,51 @@ export class InstanceManager {
|
|
271
274
|
});
|
272
275
|
}
|
273
276
|
|
274
|
-
public async startAllForPlan(systemId: string): Promise<InstanceInfo[]
|
277
|
+
public async startAllForPlan(systemId: string): Promise<Task<InstanceInfo[]>> {
|
275
278
|
systemId = normalizeKapetaUri(systemId);
|
276
279
|
const plan = await assetManager.getPlan(systemId, true);
|
277
280
|
if (!plan) {
|
278
|
-
throw new Error(
|
281
|
+
throw new Error(`Plan not found: ${systemId}`);
|
279
282
|
}
|
280
283
|
|
281
284
|
if (!plan.spec.blocks) {
|
282
|
-
|
283
|
-
return [];
|
285
|
+
throw new Error(`No blocks found in plan: ${systemId}`);
|
284
286
|
}
|
285
287
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
288
|
+
return taskManager.add(
|
289
|
+
`plan:start:${systemId}`,
|
290
|
+
async () => {
|
291
|
+
let promises: Promise<InstanceInfo>[] = [];
|
292
|
+
let errors = [];
|
293
|
+
for (let blockInstance of Object.values(plan.spec.blocks as BlockInstance[])) {
|
294
|
+
try {
|
295
|
+
promises.push(
|
296
|
+
this.start(systemId, blockInstance.id).then((taskOrInstance) => {
|
297
|
+
if (taskOrInstance instanceof Task) {
|
298
|
+
return taskOrInstance.wait();
|
299
|
+
}
|
300
|
+
return taskOrInstance;
|
301
|
+
})
|
302
|
+
);
|
303
|
+
} catch (e) {
|
304
|
+
errors.push(e);
|
305
|
+
}
|
306
|
+
}
|
295
307
|
|
296
|
-
|
308
|
+
const settled = await Promise.allSettled(promises);
|
297
309
|
|
298
|
-
|
299
|
-
|
300
|
-
|
310
|
+
if (errors.length > 0) {
|
311
|
+
throw errors[0];
|
312
|
+
}
|
301
313
|
|
302
|
-
|
314
|
+
return settled
|
315
|
+
.map((p) => (p.status === 'fulfilled' ? p.value : null))
|
316
|
+
.filter((p) => !!p) as InstanceInfo[];
|
317
|
+
},
|
318
|
+
{
|
319
|
+
name: `Starting plan ${systemId}`,
|
320
|
+
}
|
321
|
+
);
|
303
322
|
}
|
304
323
|
|
305
324
|
public async stop(systemId: string, instanceId: string) {
|
@@ -363,14 +382,21 @@ export class InstanceManager {
|
|
363
382
|
});
|
364
383
|
}
|
365
384
|
|
366
|
-
public
|
385
|
+
public stopAllForPlan(systemId: string) {
|
367
386
|
systemId = normalizeKapetaUri(systemId);
|
368
387
|
const instancesForPlan = this._instances.filter((instance) => instance.systemId === systemId);
|
369
|
-
|
370
|
-
|
388
|
+
return taskManager.add(
|
389
|
+
`plan:stop:${systemId}`,
|
390
|
+
async () => {
|
391
|
+
return this.stopInstances(instancesForPlan);
|
392
|
+
},
|
393
|
+
{
|
394
|
+
name: `Stopping plan ${systemId}`,
|
395
|
+
}
|
396
|
+
);
|
371
397
|
}
|
372
398
|
|
373
|
-
public async start(systemId: string, instanceId: string): Promise<InstanceInfo
|
399
|
+
public async start(systemId: string, instanceId: string): Promise<InstanceInfo | Task<InstanceInfo>> {
|
374
400
|
return this.exclusive(systemId, instanceId, async () => {
|
375
401
|
systemId = normalizeKapetaUri(systemId);
|
376
402
|
const plan = await assetManager.getPlan(systemId, true);
|
@@ -463,53 +489,63 @@ export class InstanceManager {
|
|
463
489
|
}
|
464
490
|
|
465
491
|
const instanceConfig = await configManager.getConfigForSection(systemId, instanceId);
|
466
|
-
const
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
492
|
+
const task = taskManager.add(
|
493
|
+
`instance:start:${systemId}:${instanceId}`,
|
494
|
+
async () => {
|
495
|
+
const runner = new BlockInstanceRunner(systemId);
|
496
|
+
const startTime = Date.now();
|
497
|
+
try {
|
498
|
+
const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
|
499
|
+
|
500
|
+
instance.status = InstanceStatus.READY;
|
501
|
+
|
502
|
+
return this.saveInternalInstance({
|
503
|
+
...instance,
|
504
|
+
type: processInfo.type,
|
505
|
+
pid: processInfo.pid ?? -1,
|
506
|
+
health: null,
|
507
|
+
portType: processInfo.portType,
|
508
|
+
status: InstanceStatus.READY,
|
509
|
+
});
|
510
|
+
} catch (e: any) {
|
511
|
+
console.warn('Failed to start instance: ', systemId, instanceId, blockRef, e.message);
|
512
|
+
const logs: LogEntry[] = [
|
513
|
+
{
|
514
|
+
source: 'stdout',
|
515
|
+
level: 'ERROR',
|
516
|
+
message: e.message,
|
517
|
+
time: Date.now(),
|
518
|
+
},
|
519
|
+
];
|
520
|
+
|
521
|
+
const out = await this.saveInternalInstance({
|
522
|
+
...instance,
|
523
|
+
type: InstanceType.UNKNOWN,
|
524
|
+
pid: null,
|
525
|
+
health: null,
|
526
|
+
portType: DEFAULT_HEALTH_PORT_TYPE,
|
527
|
+
status: InstanceStatus.FAILED,
|
528
|
+
errorMessage: e.message ?? 'Failed to start - Check logs for details.',
|
529
|
+
});
|
530
|
+
|
531
|
+
this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
|
532
|
+
|
533
|
+
this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
|
534
|
+
error: `Failed to start instance: ${e.message}`,
|
535
|
+
status: EVENT_INSTANCE_EXITED,
|
536
|
+
instanceId: blockInstance.id,
|
537
|
+
});
|
538
|
+
|
539
|
+
return out;
|
540
|
+
}
|
541
|
+
},
|
542
|
+
{
|
543
|
+
name: `Starting instance: ${instance.name}`,
|
544
|
+
systemId,
|
545
|
+
}
|
546
|
+
);
|
510
547
|
|
511
|
-
|
512
|
-
}
|
548
|
+
return task;
|
513
549
|
});
|
514
550
|
}
|
515
551
|
|
package/src/instances/routes.ts
CHANGED
@@ -6,6 +6,7 @@ import { NextFunction, Request, Response } from 'express';
|
|
6
6
|
import { kapetaHeaders, KapetaRequest } from '../middleware/kapeta';
|
7
7
|
import { stringBody } from '../middleware/stringBody';
|
8
8
|
import { DesiredInstanceStatus, InstanceInfo, InstanceOwner, InstanceType, KapetaBodyRequest } from '../types';
|
9
|
+
import { Task } from '../taskManager';
|
9
10
|
|
10
11
|
const router = Router();
|
11
12
|
router.use('/', corsHandler);
|
@@ -28,13 +29,11 @@ router.get('/:systemId/instances', (req: Request, res: Response) => {
|
|
28
29
|
* Start all instances in a plan
|
29
30
|
*/
|
30
31
|
router.post('/:systemId/start', async (req: Request, res: Response) => {
|
31
|
-
const
|
32
|
+
const task = await instanceManager.startAllForPlan(req.params.systemId);
|
32
33
|
|
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
|
|
@@ -42,10 +41,11 @@ router.post('/:systemId/start', async (req: Request, res: Response) => {
|
|
42
41
|
* Stop all instances in plan
|
43
42
|
*/
|
44
43
|
router.post('/:systemId/stop', async (req: Request, res: Response) => {
|
45
|
-
|
44
|
+
const task = instanceManager.stopAllForPlan(req.params.systemId);
|
46
45
|
|
47
46
|
res.status(202).send({
|
48
47
|
ok: true,
|
48
|
+
taskId: task.id,
|
49
49
|
});
|
50
50
|
});
|
51
51
|
|
@@ -53,13 +53,19 @@ router.post('/:systemId/stop', async (req: Request, res: Response) => {
|
|
53
53
|
* Start single instance in a plan
|
54
54
|
*/
|
55
55
|
router.post('/:systemId/:instanceId/start', async (req: Request, res: Response) => {
|
56
|
-
const
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
}
|
56
|
+
const result = await instanceManager.start(req.params.systemId, req.params.instanceId);
|
57
|
+
if (result instanceof Task) {
|
58
|
+
res.status(202).send({
|
59
|
+
ok: true,
|
60
|
+
taskId: result.id,
|
61
|
+
});
|
62
|
+
} else {
|
63
|
+
res.status(202).send({
|
64
|
+
ok: true,
|
65
|
+
pid: result.pid,
|
66
|
+
type: result.type,
|
67
|
+
});
|
68
|
+
}
|
63
69
|
});
|
64
70
|
|
65
71
|
/**
|