@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.
- package/CHANGELOG.md +7 -0
- package/dist/cjs/src/containerManager.d.ts +6 -4
- package/dist/cjs/src/containerManager.js +100 -45
- package/dist/cjs/src/definitionsManager.d.ts +1 -0
- package/dist/cjs/src/definitionsManager.js +7 -0
- package/dist/cjs/src/instanceManager.d.ts +2 -1
- package/dist/cjs/src/instanceManager.js +29 -46
- package/dist/cjs/src/instances/routes.js +10 -4
- package/dist/cjs/src/operatorManager.js +8 -6
- package/dist/cjs/src/repositoryManager.js +4 -4
- package/dist/cjs/src/types.d.ts +0 -9
- package/dist/cjs/src/utils/BlockInstanceRunner.js +9 -64
- package/dist/cjs/src/utils/utils.d.ts +1 -1
- package/dist/cjs/src/utils/utils.js +3 -2
- package/dist/esm/src/containerManager.d.ts +6 -4
- package/dist/esm/src/containerManager.js +100 -45
- package/dist/esm/src/definitionsManager.d.ts +1 -0
- package/dist/esm/src/definitionsManager.js +7 -0
- package/dist/esm/src/instanceManager.d.ts +2 -1
- package/dist/esm/src/instanceManager.js +29 -46
- package/dist/esm/src/instances/routes.js +10 -4
- package/dist/esm/src/operatorManager.js +8 -6
- package/dist/esm/src/repositoryManager.js +4 -4
- package/dist/esm/src/types.d.ts +0 -9
- package/dist/esm/src/utils/BlockInstanceRunner.js +9 -64
- package/dist/esm/src/utils/utils.d.ts +1 -1
- package/dist/esm/src/utils/utils.js +3 -2
- package/package.json +1 -1
- package/src/containerManager.ts +126 -49
- package/src/definitionsManager.ts +8 -0
- package/src/instanceManager.ts +35 -50
- package/src/instances/routes.ts +9 -4
- package/src/operatorManager.ts +9 -8
- package/src/repositoryManager.ts +5 -5
- package/src/types.ts +0 -7
- package/src/utils/BlockInstanceRunner.ts +10 -66
- package/src/utils/LogData.ts +1 -0
- package/src/utils/utils.ts +3 -2
package/dist/esm/src/types.d.ts
CHANGED
@@ -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
|
-
|
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
|
-
|
323
|
-
|
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
|
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
|
-
|
5
|
-
|
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
package/src/containerManager.ts
CHANGED
@@ -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',
|
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(
|
155
|
-
const kindUri = parseKapetaUri(
|
156
|
-
|
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
|
-
|
163
|
-
const
|
164
|
-
|
165
|
-
|
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
|
-
|
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
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
return infoData?.State?.
|
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
|
}
|
package/src/instanceManager.ts
CHANGED
@@ -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
|
-
|
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
|
package/src/instances/routes.ts
CHANGED
@@ -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
|
-
|
85
|
-
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
|
/**
|
package/src/operatorManager.ts
CHANGED
@@ -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 =
|
196
|
+
const containerName = `kapeta-resource-${md5(nameParts.join('_'))}`;
|
196
197
|
|
197
198
|
const PortBindings: { [key: string]: any } = {};
|
198
199
|
const Env: string[] = [];
|