@kapeta/local-cluster-service 0.18.0 → 0.19.1
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/src/containerManager.d.ts +15 -0
- package/dist/cjs/src/containerManager.js +189 -37
- package/dist/cjs/src/instanceManager.js +3 -8
- package/dist/cjs/src/socketManager.d.ts +2 -2
- package/dist/cjs/src/socketManager.js +39 -14
- package/dist/esm/src/containerManager.d.ts +15 -0
- package/dist/esm/src/containerManager.js +189 -37
- package/dist/esm/src/instanceManager.js +3 -8
- package/dist/esm/src/socketManager.d.ts +2 -2
- package/dist/esm/src/socketManager.js +39 -14
- package/package.json +1 -1
- package/src/containerManager.ts +208 -40
- package/src/instanceManager.ts +3 -10
- package/src/socketManager.ts +42 -15
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.19.1](https://github.com/kapetacom/local-cluster-service/compare/v0.19.0...v0.19.1) (2023-09-03)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Removed debug ([feeac46](https://github.com/kapetacom/local-cluster-service/commit/feeac462856a6a375d0f80e1098a25bbcaba0a70))
|
7
|
+
|
8
|
+
# [0.19.0](https://github.com/kapetacom/local-cluster-service/compare/v0.18.0...v0.19.0) (2023-09-03)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* Listen for docker logs when user joins room ([#67](https://github.com/kapetacom/local-cluster-service/issues/67)) ([53bd6b6](https://github.com/kapetacom/local-cluster-service/commit/53bd6b6b6de27dc2f9011887176f270d60a0d9dc))
|
14
|
+
|
1
15
|
# [0.18.0](https://github.com/kapetacom/local-cluster-service/compare/v0.17.0...v0.18.0) (2023-09-02)
|
2
16
|
|
3
17
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
/// <reference types="node" />
|
2
|
+
import FSExtra from 'fs-extra';
|
1
3
|
import { Docker } from 'node-docker-api';
|
2
4
|
import { Container } from 'node-docker-api/lib/container';
|
3
5
|
import { InstanceInfo, LogEntry } from './types';
|
@@ -50,6 +52,7 @@ declare class ContainerManager {
|
|
50
52
|
private _mountDir;
|
51
53
|
private _version;
|
52
54
|
private _lastDockerAccessCheck;
|
55
|
+
private logStreams;
|
53
56
|
constructor();
|
54
57
|
initialize(): Promise<void>;
|
55
58
|
checkAlive(): Promise<boolean>;
|
@@ -83,6 +86,17 @@ declare class ContainerManager {
|
|
83
86
|
*/
|
84
87
|
get(name: string): Promise<ContainerInfo | null>;
|
85
88
|
getLogs(instance: InstanceInfo): Promise<LogEntry[]>;
|
89
|
+
stopLogListening(systemId: string, instanceId: string): Promise<void>;
|
90
|
+
ensureLogListening(systemId: string, instanceId: string, handler: (log: LogEntry) => void): Promise<void>;
|
91
|
+
}
|
92
|
+
declare class ClosableLogStream {
|
93
|
+
private readonly stream;
|
94
|
+
private readonly eventEmitter;
|
95
|
+
constructor(stream: FSExtra.ReadStream);
|
96
|
+
onLog(listener: (log: LogEntry) => void): () => void;
|
97
|
+
onEnd(listener: () => void): () => void;
|
98
|
+
onError(listener: (error: Error) => void): () => void;
|
99
|
+
close(): Promise<void>;
|
86
100
|
}
|
87
101
|
export declare class ContainerInfo {
|
88
102
|
private readonly _container;
|
@@ -107,6 +121,7 @@ export declare class ContainerInfo {
|
|
107
121
|
inspect(): Promise<any>;
|
108
122
|
status(): Promise<DockerState>;
|
109
123
|
getPorts(): Promise<PortMap | false>;
|
124
|
+
getLogStream(): Promise<ClosableLogStream>;
|
110
125
|
getLogs(): Promise<LogEntry[]>;
|
111
126
|
}
|
112
127
|
export declare function getExtraHosts(dockerVersion: string): string[] | undefined;
|
@@ -17,6 +17,7 @@ const md5_1 = __importDefault(require("md5"));
|
|
17
17
|
const utils_1 = require("./utils/utils");
|
18
18
|
const nodejs_api_client_1 = require("@kapeta/nodejs-api-client");
|
19
19
|
const taskManager_1 = require("./taskManager");
|
20
|
+
const node_events_1 = require("node:events");
|
20
21
|
exports.CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
|
21
22
|
const NANO_SECOND = 1000000;
|
22
23
|
const HEALTH_CHECK_INTERVAL = 3000;
|
@@ -33,6 +34,7 @@ class ContainerManager {
|
|
33
34
|
_mountDir;
|
34
35
|
_version;
|
35
36
|
_lastDockerAccessCheck = 0;
|
37
|
+
logStreams = {};
|
36
38
|
constructor() {
|
37
39
|
this._docker = null;
|
38
40
|
this._alive = false;
|
@@ -472,6 +474,174 @@ class ContainerManager {
|
|
472
474
|
}
|
473
475
|
return containerInfo.getLogs();
|
474
476
|
}
|
477
|
+
async stopLogListening(systemId, instanceId) {
|
478
|
+
const containerName = (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
|
479
|
+
if (this.logStreams[containerName]) {
|
480
|
+
if (this.logStreams[containerName]?.timer) {
|
481
|
+
clearTimeout(this.logStreams[containerName].timer);
|
482
|
+
}
|
483
|
+
try {
|
484
|
+
const stream = this.logStreams[containerName].stream;
|
485
|
+
if (stream) {
|
486
|
+
await stream.close();
|
487
|
+
}
|
488
|
+
}
|
489
|
+
catch (err) {
|
490
|
+
// Ignore
|
491
|
+
}
|
492
|
+
delete this.logStreams[containerName];
|
493
|
+
}
|
494
|
+
}
|
495
|
+
async ensureLogListening(systemId, instanceId, handler) {
|
496
|
+
const containerName = (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
|
497
|
+
try {
|
498
|
+
if (this.logStreams[containerName]?.stream) {
|
499
|
+
// Already listening - will shut itself down
|
500
|
+
return;
|
501
|
+
}
|
502
|
+
if (this.logStreams[containerName]?.timer) {
|
503
|
+
clearTimeout(this.logStreams[containerName].timer);
|
504
|
+
}
|
505
|
+
const tryLater = () => {
|
506
|
+
this.logStreams[containerName] = {
|
507
|
+
timer: setTimeout(() => {
|
508
|
+
// Keep trying until user decides to not listen anymore
|
509
|
+
this.ensureLogListening(systemId, instanceId, handler);
|
510
|
+
}, 5000),
|
511
|
+
};
|
512
|
+
};
|
513
|
+
const containerInfo = await this.getContainerByName(containerName);
|
514
|
+
if (!containerInfo || !(await containerInfo.isRunning())) {
|
515
|
+
// Container not currently running - try again in 5 seconds
|
516
|
+
tryLater();
|
517
|
+
return;
|
518
|
+
}
|
519
|
+
const stream = await containerInfo.getLogStream();
|
520
|
+
stream.onLog((log) => {
|
521
|
+
try {
|
522
|
+
handler(log);
|
523
|
+
}
|
524
|
+
catch (err) {
|
525
|
+
console.warn('Error handling log', err);
|
526
|
+
}
|
527
|
+
});
|
528
|
+
stream.onEnd(() => {
|
529
|
+
// We get here if the container is stopped
|
530
|
+
delete this.logStreams[containerName];
|
531
|
+
tryLater();
|
532
|
+
});
|
533
|
+
stream.onError((err) => {
|
534
|
+
// We get here if the container crashes
|
535
|
+
delete this.logStreams[containerName];
|
536
|
+
tryLater();
|
537
|
+
});
|
538
|
+
this.logStreams[containerName] = {
|
539
|
+
stream,
|
540
|
+
};
|
541
|
+
}
|
542
|
+
catch (err) {
|
543
|
+
// Ignore
|
544
|
+
}
|
545
|
+
}
|
546
|
+
}
|
547
|
+
function readLogBuffer(logBuffer) {
|
548
|
+
const out = [];
|
549
|
+
let offset = 0;
|
550
|
+
while (offset < logBuffer.length) {
|
551
|
+
try {
|
552
|
+
// Read the docker log format - explained here:
|
553
|
+
// https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach
|
554
|
+
// or here : https://ahmet.im/blog/docker-logs-api-binary-format-explained/
|
555
|
+
// First byte is stream type
|
556
|
+
const streamTypeInt = logBuffer.readInt8(offset);
|
557
|
+
const streamType = streamTypeInt === 1 ? 'stdout' : 'stderr';
|
558
|
+
if (streamTypeInt !== 1 && streamTypeInt !== 2) {
|
559
|
+
console.error('Unknown stream type: %s', streamTypeInt, out[out.length - 1]);
|
560
|
+
break;
|
561
|
+
}
|
562
|
+
// Bytes 4-8 is frame size
|
563
|
+
const messageLength = logBuffer.readInt32BE(offset + 4);
|
564
|
+
// After that is the message - with the message length
|
565
|
+
const dataWithoutStreamType = logBuffer.subarray(offset + 8, offset + 8 + messageLength);
|
566
|
+
const raw = dataWithoutStreamType.toString();
|
567
|
+
// Split the message into date and message
|
568
|
+
const firstSpaceIx = raw.indexOf(' ');
|
569
|
+
const dateString = raw.substring(0, firstSpaceIx);
|
570
|
+
const line = raw.substring(firstSpaceIx + 1);
|
571
|
+
offset = offset + messageLength + 8;
|
572
|
+
if (!dateString) {
|
573
|
+
break;
|
574
|
+
}
|
575
|
+
out.push({
|
576
|
+
time: new Date(dateString).getTime(),
|
577
|
+
message: line,
|
578
|
+
level: 'INFO',
|
579
|
+
source: streamType,
|
580
|
+
});
|
581
|
+
}
|
582
|
+
catch (err) {
|
583
|
+
console.error('Error parsing log entry', err);
|
584
|
+
offset = logBuffer.length;
|
585
|
+
break;
|
586
|
+
}
|
587
|
+
}
|
588
|
+
return out;
|
589
|
+
}
|
590
|
+
class ClosableLogStream {
|
591
|
+
stream;
|
592
|
+
eventEmitter;
|
593
|
+
constructor(stream) {
|
594
|
+
this.stream = stream;
|
595
|
+
this.eventEmitter = new node_events_1.EventEmitter();
|
596
|
+
stream.on('data', (data) => {
|
597
|
+
const logs = readLogBuffer(data);
|
598
|
+
logs.forEach((log) => {
|
599
|
+
this.eventEmitter.emit('log', log);
|
600
|
+
});
|
601
|
+
});
|
602
|
+
stream.on('end', () => {
|
603
|
+
this.eventEmitter.emit('end');
|
604
|
+
});
|
605
|
+
stream.on('error', (error) => {
|
606
|
+
this.eventEmitter.emit('error', error);
|
607
|
+
});
|
608
|
+
stream.on('close', () => {
|
609
|
+
this.eventEmitter.emit('end');
|
610
|
+
});
|
611
|
+
}
|
612
|
+
onLog(listener) {
|
613
|
+
this.eventEmitter.on('log', listener);
|
614
|
+
return () => {
|
615
|
+
this.eventEmitter.removeListener('log', listener);
|
616
|
+
};
|
617
|
+
}
|
618
|
+
onEnd(listener) {
|
619
|
+
this.eventEmitter.on('end', listener);
|
620
|
+
return () => {
|
621
|
+
this.eventEmitter.removeListener('end', listener);
|
622
|
+
};
|
623
|
+
}
|
624
|
+
onError(listener) {
|
625
|
+
this.eventEmitter.on('error', listener);
|
626
|
+
return () => {
|
627
|
+
this.eventEmitter.removeListener('error', listener);
|
628
|
+
};
|
629
|
+
}
|
630
|
+
close() {
|
631
|
+
return new Promise((resolve, reject) => {
|
632
|
+
try {
|
633
|
+
this.stream.close((err) => {
|
634
|
+
if (err) {
|
635
|
+
console.warn('Error closing log stream', err);
|
636
|
+
}
|
637
|
+
resolve();
|
638
|
+
});
|
639
|
+
}
|
640
|
+
catch (err) {
|
641
|
+
// Ignore
|
642
|
+
}
|
643
|
+
});
|
644
|
+
}
|
475
645
|
}
|
476
646
|
class ContainerInfo {
|
477
647
|
_container;
|
@@ -555,52 +725,34 @@ class ContainerInfo {
|
|
555
725
|
});
|
556
726
|
return ports;
|
557
727
|
}
|
728
|
+
async getLogStream() {
|
729
|
+
try {
|
730
|
+
const logStream = (await this.native.logs({
|
731
|
+
stdout: true,
|
732
|
+
stderr: true,
|
733
|
+
follow: true,
|
734
|
+
tail: 0,
|
735
|
+
timestamps: true,
|
736
|
+
}));
|
737
|
+
return new ClosableLogStream(logStream);
|
738
|
+
}
|
739
|
+
catch (err) {
|
740
|
+
console.log('Error getting log stream', err);
|
741
|
+
throw err;
|
742
|
+
}
|
743
|
+
}
|
558
744
|
async getLogs() {
|
559
745
|
const logStream = (await this.native.logs({
|
560
746
|
stdout: true,
|
561
747
|
stderr: true,
|
562
748
|
follow: false,
|
563
|
-
tail: 100,
|
564
749
|
timestamps: true,
|
565
750
|
}));
|
566
|
-
const
|
751
|
+
const chunks = [];
|
567
752
|
await promisifyStream(logStream, (data) => {
|
568
|
-
|
569
|
-
let offset = 0;
|
570
|
-
while (offset < buf.length) {
|
571
|
-
try {
|
572
|
-
// Read the docker log format - explained here:
|
573
|
-
// https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach
|
574
|
-
// or here : https://ahmet.im/blog/docker-logs-api-binary-format-explained/
|
575
|
-
// First byte is stream type
|
576
|
-
const streamTypeInt = buf.readInt8(offset);
|
577
|
-
const streamType = streamTypeInt === 1 ? 'stdout' : 'stderr';
|
578
|
-
// Bytes 4-8 is frame size
|
579
|
-
const messageLength = buf.readInt32BE(offset + 4);
|
580
|
-
// After that is the message - with the message length
|
581
|
-
const dataWithoutStreamType = buf.subarray(offset + 8, offset + 8 + messageLength);
|
582
|
-
const raw = dataWithoutStreamType.toString();
|
583
|
-
// Split the message into date and message
|
584
|
-
const firstSpaceIx = raw.indexOf(' ');
|
585
|
-
const dateString = raw.substring(0, firstSpaceIx);
|
586
|
-
const line = raw.substring(firstSpaceIx + 1);
|
587
|
-
offset = offset + messageLength + 8;
|
588
|
-
if (!dateString) {
|
589
|
-
continue;
|
590
|
-
}
|
591
|
-
out.push({
|
592
|
-
time: new Date(dateString).getTime(),
|
593
|
-
message: line,
|
594
|
-
level: 'INFO',
|
595
|
-
source: streamType,
|
596
|
-
});
|
597
|
-
}
|
598
|
-
catch (err) {
|
599
|
-
console.error('Error parsing log entry', err);
|
600
|
-
offset = buf.length;
|
601
|
-
}
|
602
|
-
}
|
753
|
+
chunks.push(data);
|
603
754
|
});
|
755
|
+
const out = readLogBuffer(Buffer.concat(chunks));
|
604
756
|
if (out.length === 0) {
|
605
757
|
out.push({
|
606
758
|
time: Date.now(),
|
@@ -422,11 +422,6 @@ class InstanceManager {
|
|
422
422
|
errorMessage: e.message ?? 'Failed to start - Check logs for details.',
|
423
423
|
});
|
424
424
|
socketManager_1.socketManager.emitInstanceLog(systemId, instanceId, logs[0]);
|
425
|
-
socketManager_1.socketManager.emitInstanceEvent(systemId, blockInstance.id, socketManager_1.EVENT_INSTANCE_EXITED, {
|
426
|
-
error: `Failed to start instance: ${e.message}`,
|
427
|
-
status: socketManager_1.EVENT_INSTANCE_EXITED,
|
428
|
-
instanceId: blockInstance.id,
|
429
|
-
});
|
430
425
|
return out;
|
431
426
|
}
|
432
427
|
}, {
|
@@ -486,6 +481,7 @@ class InstanceManager {
|
|
486
481
|
try {
|
487
482
|
const plan = await assetManager_1.assetManager.getAsset(instance.systemId, true, false);
|
488
483
|
if (!plan) {
|
484
|
+
console.log('Plan not found - reset to stop', instance.ref, instance.systemId);
|
489
485
|
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
490
486
|
changed = true;
|
491
487
|
return;
|
@@ -493,12 +489,14 @@ class InstanceManager {
|
|
493
489
|
const planData = plan.data;
|
494
490
|
const planInstance = planData?.spec?.blocks?.find((b) => b.id === instance.instanceId);
|
495
491
|
if (!planInstance || !planInstance?.block?.ref) {
|
492
|
+
console.log('Plan instance not found - reset to stop', instance.ref, instance.systemId);
|
496
493
|
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
497
494
|
changed = true;
|
498
495
|
return;
|
499
496
|
}
|
500
497
|
const blockDef = await assetManager_1.assetManager.getAsset(instance.ref, true, false);
|
501
498
|
if (!blockDef) {
|
499
|
+
console.log('Block definition not found - reset to stop', instance.ref, instance.systemId);
|
502
500
|
instance.desiredStatus = types_1.DesiredInstanceStatus.STOP;
|
503
501
|
changed = true;
|
504
502
|
return;
|
@@ -676,6 +674,3 @@ class InstanceManager {
|
|
676
674
|
}
|
677
675
|
exports.InstanceManager = InstanceManager;
|
678
676
|
exports.instanceManager = new InstanceManager();
|
679
|
-
process.on('exit', async () => {
|
680
|
-
await exports.instanceManager.stopAll();
|
681
|
-
});
|
@@ -19,10 +19,10 @@ export declare class SocketManager {
|
|
19
19
|
emitInstanceLog(systemId: string, instanceId: string, payload: LogEntry): void;
|
20
20
|
emitSystemLog(systemId: string, payload: LogEntry): void;
|
21
21
|
emitGlobalLog(payload: LogEntry): void;
|
22
|
-
emitInstanceEvent(systemId: string, instanceId: string, type: string, payload: any): void;
|
23
22
|
private _bindIO;
|
24
23
|
private _handleSocketCreated;
|
25
24
|
private _bindSocket;
|
26
|
-
private
|
25
|
+
private handleJoinRoom;
|
26
|
+
private handleLeaveRoom;
|
27
27
|
}
|
28
28
|
export declare const socketManager: SocketManager;
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.socketManager = exports.SocketManager = exports.EVENT_LOG = exports.EVENT_SYSTEM_LOG = exports.EVENT_INSTANCE_LOG = exports.EVENT_INSTANCE_EXITED = exports.EVENT_INSTANCE_CREATED = exports.EVENT_STATUS_CHANGED = void 0;
|
7
7
|
const lodash_1 = __importDefault(require("lodash"));
|
8
8
|
const utils_1 = require("./utils/utils");
|
9
|
+
const containerManager_1 = require("./containerManager");
|
9
10
|
exports.EVENT_STATUS_CHANGED = 'status-changed';
|
10
11
|
exports.EVENT_INSTANCE_CREATED = 'instance-created';
|
11
12
|
exports.EVENT_INSTANCE_EXITED = 'instance-exited';
|
@@ -48,30 +49,28 @@ class SocketManager {
|
|
48
49
|
emitSystemEvent(systemId, type, payload) {
|
49
50
|
systemId = (0, utils_1.normalizeKapetaUri)(systemId);
|
50
51
|
try {
|
51
|
-
|
52
|
+
const contextId = `system-events/${encodeURIComponent(systemId)}`;
|
53
|
+
this.emit(contextId, type, payload);
|
52
54
|
}
|
53
55
|
catch (e) {
|
54
56
|
console.warn('Failed to emit instance event: %s', e.message);
|
55
57
|
}
|
56
58
|
}
|
57
59
|
emitInstanceLog(systemId, instanceId, payload) {
|
58
|
-
this.emitInstanceEvent(systemId, instanceId, exports.EVENT_INSTANCE_LOG, payload);
|
59
|
-
}
|
60
|
-
emitSystemLog(systemId, payload) {
|
61
|
-
this.emitSystemEvent(systemId, exports.EVENT_SYSTEM_LOG, payload);
|
62
|
-
}
|
63
|
-
emitGlobalLog(payload) {
|
64
|
-
this.emitGlobal(exports.EVENT_LOG, payload);
|
65
|
-
}
|
66
|
-
emitInstanceEvent(systemId, instanceId, type, payload) {
|
67
60
|
systemId = (0, utils_1.normalizeKapetaUri)(systemId);
|
68
61
|
try {
|
69
|
-
|
62
|
+
this.emit(`instance-logs/${encodeURIComponent(systemId)}/${encodeURIComponent(instanceId)}`, exports.EVENT_INSTANCE_LOG, payload);
|
70
63
|
}
|
71
64
|
catch (e) {
|
72
65
|
console.warn('Failed to emit instance event: %s', e.message);
|
73
66
|
}
|
74
67
|
}
|
68
|
+
emitSystemLog(systemId, payload) {
|
69
|
+
this.emitSystemEvent(systemId, exports.EVENT_SYSTEM_LOG, payload);
|
70
|
+
}
|
71
|
+
emitGlobalLog(payload) {
|
72
|
+
this.emitGlobal(exports.EVENT_LOG, payload);
|
73
|
+
}
|
75
74
|
_bindIO() {
|
76
75
|
this.io.on('connection', (socket) => this._handleSocketCreated(socket));
|
77
76
|
}
|
@@ -80,16 +79,42 @@ class SocketManager {
|
|
80
79
|
this._sockets.push(socket);
|
81
80
|
}
|
82
81
|
_bindSocket(socket) {
|
83
|
-
socket.on('disconnect', () =>
|
82
|
+
socket.on('disconnect', () => {
|
83
|
+
lodash_1.default.pull(this._sockets, socket);
|
84
|
+
socket.rooms.forEach((roomId) => {
|
85
|
+
this.handleLeaveRoom(roomId);
|
86
|
+
});
|
87
|
+
});
|
84
88
|
socket.on('join', (id) => {
|
85
89
|
socket.join(id);
|
90
|
+
this.handleJoinRoom(id);
|
86
91
|
});
|
87
92
|
socket.on('leave', (id) => {
|
88
93
|
socket.leave(id);
|
94
|
+
this.handleLeaveRoom(id);
|
89
95
|
});
|
90
96
|
}
|
91
|
-
|
92
|
-
|
97
|
+
handleJoinRoom(id) {
|
98
|
+
if (id.startsWith('instance-logs/')) {
|
99
|
+
let [, systemId, instanceId] = id.split(/\//g);
|
100
|
+
systemId = decodeURIComponent(systemId);
|
101
|
+
instanceId = decodeURIComponent(instanceId);
|
102
|
+
console.log('Start listening for logs', systemId, instanceId);
|
103
|
+
containerManager_1.containerManager
|
104
|
+
.ensureLogListening(systemId, instanceId, (log) => {
|
105
|
+
this.emitInstanceLog(systemId, instanceId, log);
|
106
|
+
})
|
107
|
+
.catch((e) => { });
|
108
|
+
}
|
109
|
+
}
|
110
|
+
handleLeaveRoom(id) {
|
111
|
+
if (id.startsWith('instance-logs/')) {
|
112
|
+
let [, systemId, instanceId] = id.split(/\//g);
|
113
|
+
systemId = decodeURIComponent(systemId);
|
114
|
+
instanceId = decodeURIComponent(instanceId);
|
115
|
+
console.log('Stop listening for logs', systemId, instanceId);
|
116
|
+
containerManager_1.containerManager.stopLogListening(systemId, instanceId).catch((e) => { });
|
117
|
+
}
|
93
118
|
}
|
94
119
|
}
|
95
120
|
exports.SocketManager = SocketManager;
|
@@ -1,3 +1,5 @@
|
|
1
|
+
/// <reference types="node" />
|
2
|
+
import FSExtra from 'fs-extra';
|
1
3
|
import { Docker } from 'node-docker-api';
|
2
4
|
import { Container } from 'node-docker-api/lib/container';
|
3
5
|
import { InstanceInfo, LogEntry } from './types';
|
@@ -50,6 +52,7 @@ declare class ContainerManager {
|
|
50
52
|
private _mountDir;
|
51
53
|
private _version;
|
52
54
|
private _lastDockerAccessCheck;
|
55
|
+
private logStreams;
|
53
56
|
constructor();
|
54
57
|
initialize(): Promise<void>;
|
55
58
|
checkAlive(): Promise<boolean>;
|
@@ -83,6 +86,17 @@ declare class ContainerManager {
|
|
83
86
|
*/
|
84
87
|
get(name: string): Promise<ContainerInfo | null>;
|
85
88
|
getLogs(instance: InstanceInfo): Promise<LogEntry[]>;
|
89
|
+
stopLogListening(systemId: string, instanceId: string): Promise<void>;
|
90
|
+
ensureLogListening(systemId: string, instanceId: string, handler: (log: LogEntry) => void): Promise<void>;
|
91
|
+
}
|
92
|
+
declare class ClosableLogStream {
|
93
|
+
private readonly stream;
|
94
|
+
private readonly eventEmitter;
|
95
|
+
constructor(stream: FSExtra.ReadStream);
|
96
|
+
onLog(listener: (log: LogEntry) => void): () => void;
|
97
|
+
onEnd(listener: () => void): () => void;
|
98
|
+
onError(listener: (error: Error) => void): () => void;
|
99
|
+
close(): Promise<void>;
|
86
100
|
}
|
87
101
|
export declare class ContainerInfo {
|
88
102
|
private readonly _container;
|
@@ -107,6 +121,7 @@ export declare class ContainerInfo {
|
|
107
121
|
inspect(): Promise<any>;
|
108
122
|
status(): Promise<DockerState>;
|
109
123
|
getPorts(): Promise<PortMap | false>;
|
124
|
+
getLogStream(): Promise<ClosableLogStream>;
|
110
125
|
getLogs(): Promise<LogEntry[]>;
|
111
126
|
}
|
112
127
|
export declare function getExtraHosts(dockerVersion: string): string[] | undefined;
|