@kapeta/local-cluster-service 0.10.1 → 0.11.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cjs/src/containerManager.d.ts +6 -4
  3. package/dist/cjs/src/containerManager.js +100 -45
  4. package/dist/cjs/src/definitionsManager.d.ts +1 -0
  5. package/dist/cjs/src/definitionsManager.js +7 -0
  6. package/dist/cjs/src/instanceManager.d.ts +2 -1
  7. package/dist/cjs/src/instanceManager.js +29 -46
  8. package/dist/cjs/src/instances/routes.js +10 -4
  9. package/dist/cjs/src/operatorManager.js +8 -6
  10. package/dist/cjs/src/repositoryManager.js +4 -4
  11. package/dist/cjs/src/types.d.ts +0 -9
  12. package/dist/cjs/src/utils/BlockInstanceRunner.js +9 -64
  13. package/dist/cjs/src/utils/utils.d.ts +1 -1
  14. package/dist/cjs/src/utils/utils.js +3 -2
  15. package/dist/esm/src/containerManager.d.ts +6 -4
  16. package/dist/esm/src/containerManager.js +100 -45
  17. package/dist/esm/src/definitionsManager.d.ts +1 -0
  18. package/dist/esm/src/definitionsManager.js +7 -0
  19. package/dist/esm/src/instanceManager.d.ts +2 -1
  20. package/dist/esm/src/instanceManager.js +29 -46
  21. package/dist/esm/src/instances/routes.js +10 -4
  22. package/dist/esm/src/operatorManager.js +8 -6
  23. package/dist/esm/src/repositoryManager.js +4 -4
  24. package/dist/esm/src/types.d.ts +0 -9
  25. package/dist/esm/src/utils/BlockInstanceRunner.js +9 -64
  26. package/dist/esm/src/utils/utils.d.ts +1 -1
  27. package/dist/esm/src/utils/utils.js +3 -2
  28. package/package.json +1 -1
  29. package/src/containerManager.ts +126 -49
  30. package/src/definitionsManager.ts +8 -0
  31. package/src/instanceManager.ts +35 -50
  32. package/src/instances/routes.ts +9 -4
  33. package/src/operatorManager.ts +9 -8
  34. package/src/repositoryManager.ts +5 -5
  35. package/src/types.ts +0 -7
  36. package/src/utils/BlockInstanceRunner.ts +10 -66
  37. package/src/utils/LogData.ts +1 -0
  38. package/src/utils/utils.ts +3 -2
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- import EventEmitter from 'events';
3
1
  import express from 'express';
4
2
  import { Resource } from '@kapeta/schemas';
5
3
  import { StringBodyRequest } from './middleware/stringBody';
@@ -50,10 +48,7 @@ export declare enum DesiredInstanceStatus {
50
48
  export type ProcessInfo = {
51
49
  type: InstanceType;
52
50
  pid?: number | string | null;
53
- output: EventEmitter;
54
51
  portType?: string;
55
- logs: () => LogEntry[];
56
- stop: () => Promise<void> | void;
57
52
  };
58
53
  export type InstanceInfo = {
59
54
  systemId: string;
@@ -69,10 +64,6 @@ export type InstanceInfo = {
69
64
  health?: string | null;
70
65
  pid?: number | string | null;
71
66
  portType?: string;
72
- internal?: {
73
- output: EventEmitter;
74
- logs: () => LogEntry[];
75
- };
76
67
  };
77
68
  interface ResourceRef {
78
69
  blockId: string;
@@ -5,7 +5,6 @@ import { parseKapetaUri } from '@kapeta/nodejs-utils';
5
5
  import { serviceManager } from '../serviceManager';
6
6
  import { containerManager, toLocalBindVolume } from '../containerManager';
7
7
  import { LogData } from './LogData';
8
- import EventEmitter from 'events';
9
8
  import { clusterService } from '../clusterService';
10
9
  import { InstanceType } from '../types';
11
10
  import { definitionsManager } from '../definitionsManager';
@@ -127,7 +126,7 @@ export class BlockInstanceRunner {
127
126
  if (!dockerImage) {
128
127
  throw new Error(`Missing docker image information: ${JSON.stringify(localContainer)}`);
129
128
  }
130
- const containerName = getBlockInstanceContainerName(blockInstance.id);
129
+ const containerName = getBlockInstanceContainerName(this._systemId, blockInstance.id);
131
130
  const startCmd = localContainer.handlers?.onCreate ? localContainer.handlers.onCreate : '';
132
131
  const dockerOpts = localContainer.options ?? {};
133
132
  const homeDir = localContainer.userHome ? localContainer.userHome : '/root';
@@ -180,7 +179,7 @@ export class BlockInstanceRunner {
180
179
  throw new Error(`Missing docker image information: ${JSON.stringify(versionInfo?.artifact?.details)}`);
181
180
  }
182
181
  const { PortBindings, ExposedPorts, addonEnv } = await this.getDockerPortBindings(blockInstance, assetVersion);
183
- const containerName = getBlockInstanceContainerName(blockInstance.id);
182
+ const containerName = getBlockInstanceContainerName(this._systemId, blockInstance.id);
184
183
  // For windows we need to default to root
185
184
  const innerHome = process.platform === 'win32' ? '/root/.kapeta' : ClusterConfig.getKapetaBasedir();
186
185
  return this.ensureContainer({
@@ -225,7 +224,8 @@ export class BlockInstanceRunner {
225
224
  throw new Error(`Provider did not have local image: ${providerRef}`);
226
225
  }
227
226
  const dockerImage = spec?.local?.image;
228
- const containerName = getBlockInstanceContainerName(blockInstance.id);
227
+ //We only want 1 operator per operator type - across all local systems
228
+ const containerName = getBlockInstanceContainerName(this._systemId, blockInstance.id);
229
229
  const logs = new LogData();
230
230
  const bindHost = getBindHost();
231
231
  const ExposedPorts = {};
@@ -252,7 +252,7 @@ export class BlockInstanceRunner {
252
252
  });
253
253
  }
254
254
  if (spec.local?.mounts) {
255
- const mounts = containerManager.createMounts(blockUri.id, spec.local.mounts);
255
+ const mounts = await containerManager.createMounts(this._systemId, blockUri.id, spec.local.mounts);
256
256
  Mounts = containerManager.toDockerMounts(mounts);
257
257
  }
258
258
  if (spec.local?.health) {
@@ -317,69 +317,14 @@ export class BlockInstanceRunner {
317
317
  return { PortBindings, ExposedPorts, addonEnv };
318
318
  }
319
319
  async ensureContainer(opts) {
320
- const logs = new LogData();
321
320
  const container = await containerManager.ensureContainer(opts);
322
- try {
323
- if (opts.HealthCheck) {
324
- await containerManager.waitForHealthy(container);
325
- }
326
- else {
327
- await containerManager.waitForReady(container);
328
- }
329
- }
330
- catch (e) {
331
- logs.addLog(e.message, 'ERROR');
332
- }
333
- return this._handleContainer(container, logs);
321
+ await containerManager.waitForReady(container);
322
+ return this._handleContainer(container);
334
323
  }
335
- async _handleContainer(container, logs, deleteOnExit = false) {
336
- let localContainer = container;
337
- const logStream = (await container.logs({
338
- follow: true,
339
- stdout: true,
340
- stderr: true,
341
- tail: LogData.MAX_LINES,
342
- }));
343
- const outputEvents = new EventEmitter();
344
- logStream.on('data', (data) => {
345
- logs.addLog(data.toString());
346
- outputEvents.emit('data', data);
347
- });
348
- logStream.on('error', (data) => {
349
- logs.addLog(data.toString());
350
- outputEvents.emit('data', data);
351
- });
352
- logStream.on('close', async () => {
353
- const status = await container.status();
354
- const data = status.data;
355
- if (deleteOnExit) {
356
- try {
357
- await containerManager.remove(container);
358
- }
359
- catch (e) { }
360
- }
361
- outputEvents.emit('exit', data?.State?.ExitCode ?? 0);
362
- });
324
+ async _handleContainer(container) {
363
325
  return {
364
326
  type: InstanceType.DOCKER,
365
- pid: container.id,
366
- output: outputEvents,
367
- stop: async () => {
368
- if (!localContainer) {
369
- return;
370
- }
371
- try {
372
- await localContainer.stop();
373
- if (deleteOnExit) {
374
- await containerManager.remove(localContainer);
375
- }
376
- }
377
- catch (e) { }
378
- localContainer = null;
379
- },
380
- logs: () => {
381
- return logs.getLogs();
382
- },
327
+ pid: container.id
383
328
  };
384
329
  }
385
330
  }
@@ -1,4 +1,4 @@
1
- export declare function getBlockInstanceContainerName(instanceId: string): string;
1
+ export declare function getBlockInstanceContainerName(systemId: string, instanceId: string): string;
2
2
  export declare function normalizeKapetaUri(uri: string): string;
3
3
  export declare function readYML(path: string): any;
4
4
  export declare function isWindows(): boolean;
@@ -1,8 +1,9 @@
1
1
  import FS from 'node:fs';
2
2
  import YAML from 'yaml';
3
3
  import { parseKapetaUri } from '@kapeta/nodejs-utils';
4
- export function getBlockInstanceContainerName(instanceId) {
5
- return `kapeta-block-instance-${instanceId}`;
4
+ import md5 from "md5";
5
+ export function getBlockInstanceContainerName(systemId, instanceId) {
6
+ return `kapeta-block-instance-${md5(systemId + instanceId)}`;
6
7
  }
7
8
  export function normalizeKapetaUri(uri) {
8
9
  if (!uri) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -9,6 +9,10 @@ import ClusterConfiguration from '@kapeta/local-cluster-config';
9
9
  import { Container } from 'node-docker-api/lib/container';
10
10
  import uuid from 'node-uuid';
11
11
  import md5 from 'md5';
12
+ import {getBlockInstanceContainerName} from "./utils/utils";
13
+ import {InstanceInfo, LogEntry, LogSource} from "./types";
14
+ import EventEmitter from "events";
15
+ import {LogData} from "./utils/LogData";
12
16
 
13
17
  type StringMap = { [key: string]: string };
14
18
 
@@ -63,9 +67,9 @@ const IMAGE_PULL_CACHE: { [key: string]: number } = {};
63
67
 
64
68
  export const HEALTH_CHECK_TIMEOUT = HEALTH_CHECK_INTERVAL * HEALTH_CHECK_MAX * 2;
65
69
 
66
- const promisifyStream = (stream: ReadStream) =>
70
+ const promisifyStream = (stream: ReadStream, handler:(d:string|Buffer) => void) =>
67
71
  new Promise((resolve, reject) => {
68
- stream.on('data', (d) => console.log(d.toString()));
72
+ stream.on('data', handler);
69
73
  stream.on('end', resolve);
70
74
  stream.on('error', reject);
71
75
  });
@@ -151,19 +155,30 @@ class ContainerManager {
151
155
  return this._alive;
152
156
  }
153
157
 
154
- getMountPoint(kind: string, mountName: string) {
155
- const kindUri = parseKapetaUri(kind);
156
- return Path.join(this._mountDir, kindUri.handle, kindUri.name, mountName);
158
+ getMountPoint(systemId:string, ref: string, mountName: string) {
159
+ const kindUri = parseKapetaUri(ref);
160
+ const systemUri = parseKapetaUri(systemId)
161
+ return Path.join(this._mountDir,
162
+ systemUri.handle,
163
+ systemUri.name,
164
+ systemUri.version,
165
+ kindUri.handle,
166
+ kindUri.name,
167
+ kindUri.version, mountName);
157
168
  }
158
169
 
159
- createMounts(kind: string, mountOpts: StringMap): StringMap {
170
+ async createMounts(systemId:string, kind: string, mountOpts: StringMap|null|undefined): Promise<StringMap> {
160
171
  const mounts: StringMap = {};
161
172
 
162
- _.forEach(mountOpts, (containerPath, mountName) => {
163
- const hostPath = this.getMountPoint(kind, mountName);
164
- FSExtra.mkdirpSync(hostPath);
165
- mounts[containerPath] = hostPath;
166
- });
173
+ if (mountOpts) {
174
+ const mountOptList = Object.entries(mountOpts);
175
+ for(const [mountName, containerPath] of mountOptList) {
176
+ const hostPath = this.getMountPoint(systemId, kind, mountName);
177
+ await FSExtra.mkdirp(hostPath);
178
+ mounts[containerPath] = hostPath;
179
+ }
180
+ }
181
+
167
182
  return mounts;
168
183
  }
169
184
 
@@ -224,15 +239,18 @@ class ContainerManager {
224
239
  }
225
240
 
226
241
  console.log('Pulling image: %s', image);
227
- await this.docker()
242
+ const stream = await this.docker()
228
243
  .image.create(
229
244
  {},
230
245
  {
231
246
  fromImage: imageName,
232
247
  tag: tag,
233
248
  }
234
- )
235
- .then((stream) => promisifyStream(stream as ReadStream));
249
+ ) as ReadStream;
250
+
251
+ await promisifyStream(stream, (chunk) => {
252
+ console.log('Data from docker: "%s"', chunk.toString());
253
+ });
236
254
 
237
255
  IMAGE_PULL_CACHE[image] = Date.now();
238
256
 
@@ -278,7 +296,15 @@ class ContainerManager {
278
296
  dockerOpts.Labels.HASH = hash;
279
297
  }
280
298
 
281
- async ensureContainer(opts: any) {
299
+ public async ensureContainer(opts: any) {
300
+ const container = await this.createOrUpdateContainer(opts);
301
+
302
+ await this.waitForReady(container);
303
+
304
+ return container;
305
+ }
306
+
307
+ private async createOrUpdateContainer(opts: any) {
282
308
  let imagePulled = false;
283
309
  try {
284
310
  imagePulled = await this.pull(opts.Image);
@@ -369,31 +395,6 @@ class ContainerManager {
369
395
  });
370
396
  }
371
397
 
372
- async waitForHealthy(container: Container, attempt?: number): Promise<void> {
373
- if (!attempt) {
374
- attempt = 0;
375
- }
376
-
377
- if (attempt >= HEALTH_CHECK_MAX) {
378
- throw new Error('Container did not become healthy within the timeout');
379
- }
380
-
381
- if (await this._isHealthy(container)) {
382
- return;
383
- }
384
-
385
- return new Promise((resolve, reject) => {
386
- setTimeout(async () => {
387
- try {
388
- await this.waitForHealthy(container, (attempt ?? 0) + 1);
389
- resolve();
390
- } catch (err) {
391
- reject(err);
392
- }
393
- }, HEALTH_CHECK_INTERVAL);
394
- });
395
- }
396
-
397
398
  async _isReady(container: Container) {
398
399
  let info: Container;
399
400
  try {
@@ -403,19 +404,16 @@ class ContainerManager {
403
404
  }
404
405
  const infoData: any = info?.data;
405
406
  const state = infoData?.State as DockerState;
407
+
406
408
  if (state?.Status === 'exited' || state?.Status === 'removing' || state?.Status === 'dead') {
407
409
  throw new Error('Container exited unexpectedly');
408
410
  }
409
- return infoData?.State?.Running ?? false;
410
- }
411
411
 
412
- async _isHealthy(container: Container) {
413
- try {
414
- const info = await container.status();
415
- const infoData: any = info?.data;
416
- return infoData?.State?.Health?.Status === 'healthy';
417
- } catch (err) {
418
- return false;
412
+ if (infoData?.State?.Health) {
413
+ // If container has health info - wait for it to become healthy
414
+ return infoData.State.Health.Status === 'healthy';
415
+ } else {
416
+ return infoData?.State?.Running ?? false;
419
417
  }
420
418
  }
421
419
 
@@ -449,6 +447,21 @@ class ContainerManager {
449
447
 
450
448
  return new ContainerInfo(dockerContainer);
451
449
  }
450
+
451
+ async getLogs(instance: InstanceInfo):Promise<LogEntry[]> {
452
+ const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
453
+ const containerInfo = await this.getContainerByName(containerName);
454
+ if (!containerInfo) {
455
+ return [{
456
+ source: "stdout",
457
+ level: "ERROR",
458
+ time: Date.now(),
459
+ message: "Container not found"
460
+ }];
461
+ }
462
+
463
+ return containerInfo.getLogs()
464
+ }
452
465
  }
453
466
 
454
467
  export class ContainerInfo {
@@ -558,6 +571,70 @@ export class ContainerInfo {
558
571
 
559
572
  return ports;
560
573
  }
574
+
575
+ async getLogs():Promise<LogEntry[]> {
576
+
577
+ const logStream = await this.native.logs({
578
+ stdout: true,
579
+ stderr: true,
580
+ follow: false,
581
+ tail: 100,
582
+ timestamps: true,
583
+ }) as ReadStream;
584
+
585
+ const out = [] as LogEntry[];
586
+ await promisifyStream(logStream, (data) => {
587
+ const buf = data as Buffer;
588
+ let offset = 0;
589
+ while(offset < buf.length) {
590
+ try {
591
+ // Read the docker log format - explained here:
592
+ // https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach
593
+ // or here : https://ahmet.im/blog/docker-logs-api-binary-format-explained/
594
+
595
+ // First byte is stream type
596
+ const streamTypeInt = buf.readInt8(offset);
597
+ const streamType:LogSource = streamTypeInt === 1 ? 'stdout' : 'stderr';
598
+
599
+ // Bytes 4-8 is frame size
600
+ const messageLength = buf.readInt32BE(offset + 4);
601
+
602
+ // After that is the message - with the message length
603
+ const dataWithoutStreamType = buf.subarray(offset + 8, offset + 8 + messageLength);
604
+ const raw = dataWithoutStreamType.toString();
605
+
606
+ // Split the message into date and message
607
+ const firstSpaceIx = raw.indexOf(' ');
608
+ const dateString = raw.substring(0, firstSpaceIx);
609
+ const line = raw.substring(firstSpaceIx + 1);
610
+ offset = offset + messageLength + 8;
611
+ if (!dateString) {
612
+ continue;
613
+ }
614
+ out.push({
615
+ time: new Date(dateString).getTime(),
616
+ message: line,
617
+ level: 'INFO',
618
+ source: streamType,
619
+ });
620
+ } catch (err) {
621
+ console.error('Error parsing log entry', err);
622
+ offset = buf.length
623
+ }
624
+ }
625
+ });
626
+
627
+ if (out.length === 0) {
628
+ out.push({
629
+ time: Date.now(),
630
+ message: 'No logs found for container',
631
+ level: 'INFO',
632
+ source: 'stdout',
633
+ });
634
+ }
635
+
636
+ return out;
637
+ }
561
638
  }
562
639
 
563
640
  export function getExtraHosts(dockerVersion: string): string[] | undefined {
@@ -1,4 +1,5 @@
1
1
  import ClusterConfiguration, { DefinitionInfo } from '@kapeta/local-cluster-config';
2
+ import {parseKapetaUri} from "@kapeta/nodejs-utils";
2
3
 
3
4
  const CACHE_TTL = 60 * 1000; // 1 min
4
5
 
@@ -46,6 +47,13 @@ class DefinitionsManager {
46
47
  return this.doCached(key, () => ClusterConfiguration.getDefinitions(kindFilter));
47
48
  }
48
49
 
50
+ public exists(ref: string) {
51
+ const uri = parseKapetaUri(ref);
52
+ return !!this.getDefinitions().find((d) => {
53
+ return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
54
+ });
55
+ }
56
+
49
57
  public getProviderDefinitions() {
50
58
  return this.doCached('providers', () => ClusterConfiguration.getProviderDefinitions());
51
59
  }
@@ -67,6 +67,37 @@ export class InstanceManager {
67
67
  return this._instances.find((i) => i.systemId === systemId && i.instanceId === instanceId);
68
68
  }
69
69
 
70
+
71
+ public async getLogs(systemId: string, instanceId: string):Promise<LogEntry[]> {
72
+ const instance = this.getInstance(systemId, instanceId);
73
+ if (!instance) {
74
+ throw new Error(`Instance ${systemId}/${instanceId} not found`);
75
+ }
76
+
77
+ switch (instance.type) {
78
+ case InstanceType.DOCKER:
79
+ return await containerManager.getLogs(instance);
80
+
81
+ case InstanceType.UNKNOWN:
82
+ return [{
83
+ level: 'INFO',
84
+ message: 'Instance is starting...',
85
+ time: Date.now(),
86
+ source: 'stdout',
87
+ }];
88
+
89
+ case InstanceType.LOCAL:
90
+ return [{
91
+ level: 'INFO',
92
+ message: 'Instance started outside Kapeta - logs not available...',
93
+ time: Date.now(),
94
+ source: 'stdout',
95
+ }];
96
+ }
97
+
98
+ return [];
99
+ }
100
+
70
101
  public async saveInternalInstance(instance: InstanceInfo) {
71
102
  instance.systemId = normalizeKapetaUri(instance.systemId);
72
103
  if (instance.ref) {
@@ -143,7 +174,6 @@ export class InstanceManager {
143
174
 
144
175
  instance.desiredStatus = info.desiredStatus;
145
176
  instance.owner = info.owner;
146
- instance.internal = undefined;
147
177
  instance.status = InstanceStatus.STARTING;
148
178
  instance.startedAt = Date.now();
149
179
  }
@@ -260,7 +290,7 @@ export class InstanceManager {
260
290
 
261
291
  try {
262
292
  if (instance.type === 'docker') {
263
- const containerName = getBlockInstanceContainerName(instance.instanceId);
293
+ const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
264
294
  const container = await containerManager.getContainerByName(containerName);
265
295
  if (container) {
266
296
  try {
@@ -350,7 +380,7 @@ export class InstanceManager {
350
380
  name: blockAsset.data.metadata.name,
351
381
  desiredStatus: DesiredInstanceStatus.RUN,
352
382
  owner: InstanceOwner.INTERNAL,
353
- type: InstanceType.UNKNOWN,
383
+ type: existingInstance?.type ?? InstanceType.UNKNOWN,
354
384
  status: InstanceStatus.STARTING,
355
385
  startedAt: Date.now(),
356
386
  };
@@ -374,45 +404,6 @@ export class InstanceManager {
374
404
  const startTime = Date.now();
375
405
  try {
376
406
  const processInfo = await runner.start(blockRef, instanceId, instanceConfig);
377
- //emit stdout/stderr via sockets
378
- processInfo.output.on('data', (data: Buffer) => {
379
- const payload = {
380
- source: 'stdout',
381
- level: 'INFO',
382
- message: data.toString(),
383
- time: Date.now(),
384
- };
385
- this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, payload);
386
- });
387
-
388
- processInfo.output.on('exit', (exitCode: number) => {
389
- const timeRunning = Date.now() - startTime;
390
- const instance = this.getInstance(systemId, instanceId);
391
- if (instance?.status === InstanceStatus.READY) {
392
- //It's already been running
393
- return;
394
- }
395
-
396
- if (exitCode === 143 || exitCode === 137) {
397
- //Process got SIGTERM (143) or SIGKILL (137)
398
- //TODO: Windows?
399
- return;
400
- }
401
-
402
- if (exitCode !== 0 || timeRunning < MIN_TIME_RUNNING) {
403
- const instance = this.getInstance(systemId, instanceId);
404
- if (instance) {
405
- instance.status = InstanceStatus.FAILED;
406
- this.save();
407
- }
408
-
409
- this.emitSystemEvent(systemId, EVENT_INSTANCE_EXITED, {
410
- error: 'Failed to start instance',
411
- status: EVENT_INSTANCE_EXITED,
412
- instanceId: blockInstance.id,
413
- });
414
- }
415
- });
416
407
 
417
408
  instance.status = InstanceStatus.READY;
418
409
 
@@ -423,10 +414,6 @@ export class InstanceManager {
423
414
  health: null,
424
415
  portType: processInfo.portType,
425
416
  status: InstanceStatus.READY,
426
- internal: {
427
- logs: processInfo.logs,
428
- output: processInfo.output,
429
- },
430
417
  });
431
418
  } catch (e: any) {
432
419
  console.warn('Failed to start instance', e);
@@ -482,9 +469,7 @@ export class InstanceManager {
482
469
  storageService.put(
483
470
  'instances',
484
471
  this._instances.map((instance) => {
485
- const copy = { ...instance };
486
- delete copy.internal;
487
- return copy;
472
+ return { ...instance };
488
473
  })
489
474
  );
490
475
  } catch (e) {
@@ -612,7 +597,7 @@ export class InstanceManager {
612
597
 
613
598
  private async getExternalStatus(instance: InstanceInfo): Promise<InstanceStatus> {
614
599
  if (instance.type === InstanceType.DOCKER) {
615
- const containerName = getBlockInstanceContainerName(instance.instanceId);
600
+ const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
616
601
  const container = await containerManager.getContainerByName(containerName);
617
602
  if (!container) {
618
603
  // If the container doesn't exist, we consider the instance stopped
@@ -74,16 +74,21 @@ router.post('/:systemId/:instanceId/stop', async (req: Request, res: Response) =
74
74
  /**
75
75
  * Get logs for instance in a plan
76
76
  */
77
- router.get('/:systemId/:instanceId/logs', (req: Request, res: Response) => {
77
+ router.get('/:systemId/:instanceId/logs', async (req: Request, res: Response) => {
78
78
  const instanceInfo = instanceManager.getInstance(req.params.systemId, req.params.instanceId);
79
79
  if (!instanceInfo) {
80
80
  res.status(404).send({ ok: false });
81
81
  return;
82
82
  }
83
83
 
84
- res.status(202).send({
85
- logs: instanceInfo.internal?.logs() ?? [],
86
- });
84
+ try {
85
+ const logs = await instanceManager.getLogs(req.params.systemId, req.params.instanceId);
86
+ res.status(200).send({
87
+ logs,
88
+ });
89
+ } catch (e:any) {
90
+ res.status(500).send({ ok: false, error: e.message });
91
+ }
87
92
  });
88
93
 
89
94
  /**
@@ -162,16 +162,12 @@ class OperatorManager {
162
162
 
163
163
  portTypes.sort();
164
164
 
165
- const containerBaseName = 'kapeta-resource';
166
-
167
- const nameParts = [resourceType.toLowerCase()];
168
-
169
165
  const ports: AnyMap = {};
170
166
 
171
167
  for (let i = 0; i < portTypes.length; i++) {
172
168
  const portType = portTypes[i];
173
169
  let containerPortInfo = operatorData.ports[portType];
174
- const hostPort = await serviceManager.ensureServicePort(resourceType, portType);
170
+ const hostPort = await serviceManager.ensureServicePort(systemId, resourceType, portType);
175
171
 
176
172
  if (typeof containerPortInfo === 'number' || typeof containerPortInfo === 'string') {
177
173
  containerPortInfo = { port: containerPortInfo, type: 'tcp' };
@@ -182,7 +178,6 @@ class OperatorManager {
182
178
  }
183
179
 
184
180
  const portId = containerPortInfo.port + '/' + containerPortInfo.type;
185
- nameParts.push(portType + '-' + portId + '-' + hostPort);
186
181
 
187
182
  ports[portId] = {
188
183
  type: portType,
@@ -190,9 +185,15 @@ class OperatorManager {
190
185
  };
191
186
  }
192
187
 
193
- const mounts = containerManager.createMounts(resourceType, operatorData.mounts);
188
+ const mounts = await containerManager.createMounts(systemId, resourceType, operatorData.mounts);
189
+
190
+ const nameParts = [
191
+ systemId,
192
+ resourceType.toLowerCase(),
193
+ version
194
+ ];
194
195
 
195
- const containerName = containerBaseName + '-' + md5(nameParts.join('_'));
196
+ const containerName = `kapeta-resource-${md5(nameParts.join('_'))}`;
196
197
 
197
198
  const PortBindings: { [key: string]: any } = {};
198
199
  const Env: string[] = [];