@kapeta/local-cluster-service 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/definitions.d.ts +7 -0
- package/dist/cjs/src/config/routes.js +1 -1
- package/dist/cjs/src/containerManager.d.ts +2 -1
- package/dist/cjs/src/containerManager.js +125 -21
- package/dist/cjs/src/definitionsManager.d.ts +1 -0
- package/dist/cjs/src/definitionsManager.js +7 -4
- package/dist/cjs/src/instanceManager.d.ts +12 -2
- package/dist/cjs/src/instanceManager.js +253 -200
- package/dist/cjs/src/operatorManager.d.ts +2 -0
- package/dist/cjs/src/operatorManager.js +69 -67
- package/dist/cjs/src/socketManager.d.ts +1 -0
- package/dist/cjs/src/socketManager.js +3 -0
- package/dist/cjs/src/types.d.ts +1 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +2 -2
- package/dist/esm/src/config/routes.js +1 -1
- package/dist/esm/src/containerManager.d.ts +2 -1
- package/dist/esm/src/containerManager.js +126 -22
- package/dist/esm/src/definitionsManager.d.ts +1 -0
- package/dist/esm/src/definitionsManager.js +8 -5
- package/dist/esm/src/instanceManager.d.ts +12 -2
- package/dist/esm/src/instanceManager.js +253 -200
- package/dist/esm/src/operatorManager.d.ts +2 -0
- package/dist/esm/src/operatorManager.js +67 -65
- package/dist/esm/src/socketManager.d.ts +1 -0
- package/dist/esm/src/socketManager.js +3 -0
- package/dist/esm/src/types.d.ts +1 -0
- package/dist/esm/src/utils/BlockInstanceRunner.js +2 -2
- package/dist/esm/src/utils/utils.js +1 -1
- package/package.json +3 -1
- package/src/config/routes.ts +1 -1
- package/src/containerManager.ts +178 -43
- package/src/definitionsManager.ts +9 -5
- package/src/instanceManager.ts +288 -228
- package/src/instances/routes.ts +1 -1
- package/src/operatorManager.ts +72 -70
- package/src/socketManager.ts +4 -0
- package/src/types.ts +1 -1
- package/src/utils/BlockInstanceRunner.ts +12 -22
- package/src/utils/utils.ts +2 -2
@@ -8,7 +8,8 @@ import FSExtra from 'fs-extra';
|
|
8
8
|
import { definitionsManager } from './definitionsManager';
|
9
9
|
import { getBindHost, normalizeKapetaUri } from './utils/utils';
|
10
10
|
import _ from 'lodash';
|
11
|
-
|
11
|
+
import AsyncLock from 'async-lock';
|
12
|
+
export const KIND_OPERATOR = 'core/resource-type-operator';
|
12
13
|
class Operator {
|
13
14
|
_data;
|
14
15
|
constructor(data) {
|
@@ -23,6 +24,7 @@ class Operator {
|
|
23
24
|
}
|
24
25
|
class OperatorManager {
|
25
26
|
_mountDir;
|
27
|
+
operatorLock = new AsyncLock();
|
26
28
|
constructor() {
|
27
29
|
this._mountDir = Path.join(storageService.getKapetaBasedir(), 'mounts');
|
28
30
|
FSExtra.mkdirpSync(this._mountDir);
|
@@ -107,74 +109,74 @@ class OperatorManager {
|
|
107
109
|
* @return {Promise<ContainerInfo>}
|
108
110
|
*/
|
109
111
|
async ensureResource(systemId, resourceType, version) {
|
110
|
-
|
111
|
-
const
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
containerPortInfo =
|
121
|
-
|
122
|
-
|
123
|
-
|
112
|
+
systemId = normalizeKapetaUri(systemId);
|
113
|
+
const key = `${systemId}#${resourceType}:${version}`;
|
114
|
+
return await this.operatorLock.acquire(key, async () => {
|
115
|
+
const operator = this.getOperator(resourceType, version);
|
116
|
+
const operatorData = operator.getData();
|
117
|
+
const portTypes = Object.keys(operatorData.ports);
|
118
|
+
portTypes.sort();
|
119
|
+
const ports = {};
|
120
|
+
for (let i = 0; i < portTypes.length; i++) {
|
121
|
+
const portType = portTypes[i];
|
122
|
+
let containerPortInfo = operatorData.ports[portType];
|
123
|
+
const hostPort = await serviceManager.ensureServicePort(systemId, resourceType, portType);
|
124
|
+
if (typeof containerPortInfo === 'number' || typeof containerPortInfo === 'string') {
|
125
|
+
containerPortInfo = { port: containerPortInfo, type: 'tcp' };
|
126
|
+
}
|
127
|
+
if (!containerPortInfo.type) {
|
128
|
+
containerPortInfo.type = 'tcp';
|
129
|
+
}
|
130
|
+
const portId = containerPortInfo.port + '/' + containerPortInfo.type;
|
131
|
+
ports[portId] = {
|
132
|
+
type: portType,
|
133
|
+
hostPort,
|
134
|
+
};
|
124
135
|
}
|
125
|
-
const
|
126
|
-
|
127
|
-
|
128
|
-
|
136
|
+
const mounts = await containerManager.createMounts(systemId, resourceType, operatorData.mounts);
|
137
|
+
const nameParts = [systemId, resourceType.toLowerCase(), version];
|
138
|
+
const containerName = `kapeta-resource-${md5(nameParts.join('_'))}`;
|
139
|
+
const PortBindings = {};
|
140
|
+
const Env = [];
|
141
|
+
const Labels = {
|
142
|
+
kapeta: 'true',
|
129
143
|
};
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
144
|
+
const bindHost = getBindHost();
|
145
|
+
const ExposedPorts = {};
|
146
|
+
_.forEach(ports, (portInfo, containerPort) => {
|
147
|
+
ExposedPorts['' + containerPort] = {};
|
148
|
+
PortBindings['' + containerPort] = [
|
149
|
+
{
|
150
|
+
HostPort: '' + portInfo.hostPort,
|
151
|
+
HostIp: bindHost,
|
152
|
+
},
|
153
|
+
];
|
154
|
+
Labels[CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
155
|
+
});
|
156
|
+
const Mounts = containerManager.toDockerMounts(mounts);
|
157
|
+
_.forEach(operatorData.env, (value, name) => {
|
158
|
+
Env.push(name + '=' + value);
|
159
|
+
});
|
160
|
+
let HealthCheck = undefined;
|
161
|
+
if (operatorData.health) {
|
162
|
+
HealthCheck = containerManager.toDockerHealth(operatorData.health);
|
163
|
+
}
|
164
|
+
const container = await containerManager.ensureContainer({
|
165
|
+
name: containerName,
|
166
|
+
Image: operatorData.image,
|
167
|
+
Hostname: containerName + '.kapeta',
|
168
|
+
Labels,
|
169
|
+
Cmd: operatorData.cmd,
|
170
|
+
ExposedPorts,
|
171
|
+
Env,
|
172
|
+
HealthCheck,
|
173
|
+
HostConfig: {
|
174
|
+
PortBindings,
|
175
|
+
Mounts,
|
151
176
|
},
|
152
|
-
|
153
|
-
|
154
|
-
});
|
155
|
-
const Mounts = containerManager.toDockerMounts(mounts);
|
156
|
-
_.forEach(operatorData.env, (value, name) => {
|
157
|
-
Env.push(name + '=' + value);
|
158
|
-
});
|
159
|
-
let HealthCheck = undefined;
|
160
|
-
if (operatorData.health) {
|
161
|
-
HealthCheck = containerManager.toDockerHealth(operatorData.health);
|
162
|
-
}
|
163
|
-
const container = await containerManager.ensureContainer({
|
164
|
-
name: containerName,
|
165
|
-
Image: operatorData.image,
|
166
|
-
Hostname: containerName + '.kapeta',
|
167
|
-
Labels,
|
168
|
-
Cmd: operatorData.cmd,
|
169
|
-
ExposedPorts,
|
170
|
-
Env,
|
171
|
-
HealthCheck,
|
172
|
-
HostConfig: {
|
173
|
-
PortBindings,
|
174
|
-
Mounts,
|
175
|
-
},
|
177
|
+
});
|
178
|
+
return new ContainerInfo(container);
|
176
179
|
});
|
177
|
-
return new ContainerInfo(container);
|
178
180
|
}
|
179
181
|
}
|
180
182
|
export const operatorManager = new OperatorManager();
|
@@ -7,6 +7,7 @@ export declare class SocketManager {
|
|
7
7
|
isAlive(): boolean;
|
8
8
|
private get io();
|
9
9
|
emit(context: string, type: string, payload: any): void;
|
10
|
+
emitGlobal(type: string, payload: any): void;
|
10
11
|
_bindIO(): void;
|
11
12
|
_handleSocketCreated(socket: Socket): void;
|
12
13
|
_bindSocket(socket: Socket): void;
|
@@ -23,6 +23,9 @@ export class SocketManager {
|
|
23
23
|
emit(context, type, payload) {
|
24
24
|
this.io.to(context).emit(type, { context, payload });
|
25
25
|
}
|
26
|
+
emitGlobal(type, payload) {
|
27
|
+
this.io.emit(type, { payload });
|
28
|
+
}
|
26
29
|
_bindIO() {
|
27
30
|
this.io.on('connection', (socket) => this._handleSocketCreated(socket));
|
28
31
|
}
|
package/dist/esm/src/types.d.ts
CHANGED
@@ -194,7 +194,7 @@ export class BlockInstanceRunner {
|
|
194
194
|
`KAPETA_LOCAL_CLUSTER_PORT=${clusterService.getClusterServicePort()}`,
|
195
195
|
...Object.entries({
|
196
196
|
...env,
|
197
|
-
...addonEnv
|
197
|
+
...addonEnv,
|
198
198
|
}).map(([key, value]) => `${key}=${value}`),
|
199
199
|
],
|
200
200
|
HostConfig: {
|
@@ -324,7 +324,7 @@ export class BlockInstanceRunner {
|
|
324
324
|
async _handleContainer(container) {
|
325
325
|
return {
|
326
326
|
type: InstanceType.DOCKER,
|
327
|
-
pid: container.id
|
327
|
+
pid: container.id,
|
328
328
|
};
|
329
329
|
}
|
330
330
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import FS from 'node:fs';
|
2
2
|
import YAML from 'yaml';
|
3
3
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
4
|
-
import md5 from
|
4
|
+
import md5 from 'md5';
|
5
5
|
export function getBlockInstanceContainerName(systemId, instanceId) {
|
6
6
|
return `kapeta-block-instance-${md5(systemId + instanceId)}`;
|
7
7
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.12.0",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -50,6 +50,7 @@
|
|
50
50
|
"@kapeta/nodejs-utils": "<2",
|
51
51
|
"@kapeta/schemas": "^0.0.58",
|
52
52
|
"@kapeta/sdk-config": "<2",
|
53
|
+
"async-lock": "^1.4.0",
|
53
54
|
"express": "4.17.1",
|
54
55
|
"express-promise-router": "^4.1.1",
|
55
56
|
"fs-extra": "^11.1.0",
|
@@ -70,6 +71,7 @@
|
|
70
71
|
"@kapeta/eslint-config": "^0.6.1",
|
71
72
|
"@kapeta/prettier-config": "^0.6.0",
|
72
73
|
"@tsconfig/node18": "^18.2.0",
|
74
|
+
"@types/async-lock": "^1.4.0",
|
73
75
|
"@types/express": "^4.17.17",
|
74
76
|
"@types/fs-extra": "^11.0.1",
|
75
77
|
"@types/lodash": "^4.14.195",
|
package/src/config/routes.ts
CHANGED
@@ -41,7 +41,7 @@ router.put('/instance', async (req: KapetaBodyRequest, res) => {
|
|
41
41
|
if (req.kapeta!.instanceId) {
|
42
42
|
configManager.setConfigForSection(req.kapeta!.systemId, req.kapeta!.instanceId, config);
|
43
43
|
//Restart the instance if it is running after config change
|
44
|
-
await instanceManager.
|
44
|
+
await instanceManager.prepareForRestart(req.kapeta!.systemId, req.kapeta!.instanceId);
|
45
45
|
} else {
|
46
46
|
configManager.setConfigForSystem(req.kapeta!.systemId, config);
|
47
47
|
}
|
package/src/containerManager.ts
CHANGED
@@ -9,10 +9,14 @@ 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
|
13
|
-
import {InstanceInfo, LogEntry, LogSource} from
|
14
|
-
import
|
15
|
-
import {
|
12
|
+
import { getBlockInstanceContainerName } from './utils/utils';
|
13
|
+
import { InstanceInfo, LogEntry, LogSource } from './types';
|
14
|
+
import { socketManager } from './socketManager';
|
15
|
+
import { handlers as ArtifactHandlers } from '@kapeta/nodejs-registry-utils';
|
16
|
+
import { progressListener } from './progressListener';
|
17
|
+
import { KapetaAPI } from '@kapeta/nodejs-api-client';
|
18
|
+
|
19
|
+
const EVENT_IMAGE_PULL = 'docker-image-pull';
|
16
20
|
|
17
21
|
type StringMap = { [key: string]: string };
|
18
22
|
|
@@ -67,7 +71,7 @@ const IMAGE_PULL_CACHE: { [key: string]: number } = {};
|
|
67
71
|
|
68
72
|
export const HEALTH_CHECK_TIMEOUT = HEALTH_CHECK_INTERVAL * HEALTH_CHECK_MAX * 2;
|
69
73
|
|
70
|
-
const promisifyStream = (stream: ReadStream, handler:(d:string|Buffer) => void) =>
|
74
|
+
const promisifyStream = (stream: ReadStream, handler: (d: string | Buffer) => void) =>
|
71
75
|
new Promise((resolve, reject) => {
|
72
76
|
stream.on('data', handler);
|
73
77
|
stream.on('end', resolve);
|
@@ -79,6 +83,7 @@ class ContainerManager {
|
|
79
83
|
private _alive: boolean;
|
80
84
|
private _mountDir: string;
|
81
85
|
private _version: string;
|
86
|
+
private _lastDockerAccessCheck: number = 0;
|
82
87
|
|
83
88
|
constructor() {
|
84
89
|
this._docker = null;
|
@@ -155,24 +160,27 @@ class ContainerManager {
|
|
155
160
|
return this._alive;
|
156
161
|
}
|
157
162
|
|
158
|
-
getMountPoint(systemId:string, ref: string, mountName: string) {
|
163
|
+
getMountPoint(systemId: string, ref: string, mountName: string) {
|
159
164
|
const kindUri = parseKapetaUri(ref);
|
160
|
-
const systemUri = parseKapetaUri(systemId)
|
161
|
-
return Path.join(
|
165
|
+
const systemUri = parseKapetaUri(systemId);
|
166
|
+
return Path.join(
|
167
|
+
this._mountDir,
|
162
168
|
systemUri.handle,
|
163
169
|
systemUri.name,
|
164
170
|
systemUri.version,
|
165
171
|
kindUri.handle,
|
166
172
|
kindUri.name,
|
167
|
-
kindUri.version,
|
173
|
+
kindUri.version,
|
174
|
+
mountName
|
175
|
+
);
|
168
176
|
}
|
169
177
|
|
170
|
-
async createMounts(systemId:string, kind: string, mountOpts: StringMap|null|undefined): Promise<StringMap> {
|
178
|
+
async createMounts(systemId: string, kind: string, mountOpts: StringMap | null | undefined): Promise<StringMap> {
|
171
179
|
const mounts: StringMap = {};
|
172
180
|
|
173
181
|
if (mountOpts) {
|
174
182
|
const mountOptList = Object.entries(mountOpts);
|
175
|
-
for(const [mountName, containerPath] of mountOptList) {
|
183
|
+
for (const [mountName, containerPath] of mountOptList) {
|
176
184
|
const hostPath = this.getMountPoint(systemId, kind, mountName);
|
177
185
|
await FSExtra.mkdirp(hostPath);
|
178
186
|
mounts[containerPath] = hostPath;
|
@@ -238,23 +246,153 @@ class ContainerManager {
|
|
238
246
|
return false;
|
239
247
|
}
|
240
248
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
249
|
+
const timeStarted = Date.now();
|
250
|
+
socketManager.emitGlobal(EVENT_IMAGE_PULL, { image, percent: 0 });
|
251
|
+
|
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
|
+
}
|
299
|
+
|
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
|
+
}
|
313
|
+
|
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
|
+
});
|
340
|
+
|
341
|
+
if (Date.now() - lastEmitted < 1000) {
|
342
|
+
return;
|
343
|
+
}
|
344
|
+
|
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
|
+
};
|
358
|
+
|
359
|
+
chunkList.forEach((chunk) => {
|
360
|
+
if (chunk.downloading.current > 0) {
|
361
|
+
totals.downloading.current += chunk.downloading.current;
|
362
|
+
}
|
363
|
+
|
364
|
+
if (chunk.downloading.total > 0) {
|
365
|
+
totals.downloading.total += chunk.downloading.total;
|
366
|
+
}
|
367
|
+
|
368
|
+
if (chunk.extracting.current > 0) {
|
369
|
+
totals.extracting.current += chunk.extracting.current;
|
370
|
+
}
|
371
|
+
|
372
|
+
if (chunk.extracting.total > 0) {
|
373
|
+
totals.extracting.total += chunk.extracting.total;
|
374
|
+
}
|
375
|
+
|
376
|
+
if (chunk.done) {
|
377
|
+
totals.done++;
|
248
378
|
}
|
249
|
-
)
|
379
|
+
});
|
250
380
|
|
251
|
-
|
252
|
-
|
381
|
+
const percent = totals.total > 0 ? (totals.done / totals.total) * 100 : 0;
|
382
|
+
//We emit at most every second to not spam the client
|
383
|
+
socketManager.emitGlobal(EVENT_IMAGE_PULL, {
|
384
|
+
image,
|
385
|
+
percent,
|
386
|
+
status: totals,
|
387
|
+
timeTaken: Date.now() - timeStarted,
|
388
|
+
});
|
389
|
+
lastEmitted = Date.now();
|
390
|
+
//console.log('Pulling image %s: %s % [done: %s, total: %s]', image, Math.round(percent), totals.done, totals.total);
|
253
391
|
});
|
254
392
|
|
255
393
|
IMAGE_PULL_CACHE[image] = Date.now();
|
256
394
|
|
257
|
-
|
395
|
+
socketManager.emitGlobal(EVENT_IMAGE_PULL, { image, percent: 100, timeTaken: Date.now() - timeStarted });
|
258
396
|
|
259
397
|
return true;
|
260
398
|
}
|
@@ -305,12 +443,7 @@ class ContainerManager {
|
|
305
443
|
}
|
306
444
|
|
307
445
|
private async createOrUpdateContainer(opts: any) {
|
308
|
-
let imagePulled =
|
309
|
-
try {
|
310
|
-
imagePulled = await this.pull(opts.Image);
|
311
|
-
} catch (e) {
|
312
|
-
console.warn('Failed to pull image. Continuing...', e);
|
313
|
-
}
|
446
|
+
let imagePulled = await this.pull(opts.Image);
|
314
447
|
|
315
448
|
this.applyHash(opts);
|
316
449
|
if (!opts.name) {
|
@@ -448,24 +581,27 @@ class ContainerManager {
|
|
448
581
|
return new ContainerInfo(dockerContainer);
|
449
582
|
}
|
450
583
|
|
451
|
-
async getLogs(instance: InstanceInfo):Promise<LogEntry[]> {
|
584
|
+
async getLogs(instance: InstanceInfo): Promise<LogEntry[]> {
|
452
585
|
const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
|
453
586
|
const containerInfo = await this.getContainerByName(containerName);
|
454
587
|
if (!containerInfo) {
|
455
|
-
return [
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
588
|
+
return [
|
589
|
+
{
|
590
|
+
source: 'stdout',
|
591
|
+
level: 'ERROR',
|
592
|
+
time: Date.now(),
|
593
|
+
message: 'Container not found',
|
594
|
+
},
|
595
|
+
];
|
461
596
|
}
|
462
597
|
|
463
|
-
return containerInfo.getLogs()
|
598
|
+
return containerInfo.getLogs();
|
464
599
|
}
|
465
600
|
}
|
466
601
|
|
467
602
|
export class ContainerInfo {
|
468
603
|
private readonly _container: Container;
|
604
|
+
|
469
605
|
/**
|
470
606
|
*
|
471
607
|
* @param {Container} dockerContainer
|
@@ -572,21 +708,20 @@ export class ContainerInfo {
|
|
572
708
|
return ports;
|
573
709
|
}
|
574
710
|
|
575
|
-
async getLogs():Promise<LogEntry[]> {
|
576
|
-
|
577
|
-
const logStream = await this.native.logs({
|
711
|
+
async getLogs(): Promise<LogEntry[]> {
|
712
|
+
const logStream = (await this.native.logs({
|
578
713
|
stdout: true,
|
579
714
|
stderr: true,
|
580
715
|
follow: false,
|
581
716
|
tail: 100,
|
582
717
|
timestamps: true,
|
583
|
-
})
|
718
|
+
})) as ReadStream;
|
584
719
|
|
585
720
|
const out = [] as LogEntry[];
|
586
721
|
await promisifyStream(logStream, (data) => {
|
587
722
|
const buf = data as Buffer;
|
588
723
|
let offset = 0;
|
589
|
-
while(offset < buf.length) {
|
724
|
+
while (offset < buf.length) {
|
590
725
|
try {
|
591
726
|
// Read the docker log format - explained here:
|
592
727
|
// https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach
|
@@ -594,7 +729,7 @@ export class ContainerInfo {
|
|
594
729
|
|
595
730
|
// First byte is stream type
|
596
731
|
const streamTypeInt = buf.readInt8(offset);
|
597
|
-
const streamType:LogSource = streamTypeInt === 1 ? 'stdout' : 'stderr';
|
732
|
+
const streamType: LogSource = streamTypeInt === 1 ? 'stdout' : 'stderr';
|
598
733
|
|
599
734
|
// Bytes 4-8 is frame size
|
600
735
|
const messageLength = buf.readInt32BE(offset + 4);
|
@@ -619,7 +754,7 @@ export class ContainerInfo {
|
|
619
754
|
});
|
620
755
|
} catch (err) {
|
621
756
|
console.error('Error parsing log entry', err);
|
622
|
-
offset = buf.length
|
757
|
+
offset = buf.length;
|
623
758
|
}
|
624
759
|
}
|
625
760
|
});
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import ClusterConfiguration, { DefinitionInfo } from '@kapeta/local-cluster-config';
|
2
|
-
import {parseKapetaUri} from
|
2
|
+
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
3
3
|
|
4
4
|
const CACHE_TTL = 60 * 1000; // 1 min
|
5
5
|
|
@@ -48,15 +48,19 @@ class DefinitionsManager {
|
|
48
48
|
}
|
49
49
|
|
50
50
|
public exists(ref: string) {
|
51
|
-
|
52
|
-
return !!this.getDefinitions().find((d) => {
|
53
|
-
return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
|
54
|
-
});
|
51
|
+
return !!this.getDefinition(ref);
|
55
52
|
}
|
56
53
|
|
57
54
|
public getProviderDefinitions() {
|
58
55
|
return this.doCached('providers', () => ClusterConfiguration.getProviderDefinitions());
|
59
56
|
}
|
57
|
+
|
58
|
+
public getDefinition(ref: string) {
|
59
|
+
const uri = parseKapetaUri(ref);
|
60
|
+
return this.getDefinitions().find((d) => {
|
61
|
+
return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
|
62
|
+
});
|
63
|
+
}
|
60
64
|
}
|
61
65
|
|
62
66
|
export const definitionsManager = new DefinitionsManager();
|