@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/definitions.d.ts +7 -0
  3. package/dist/cjs/src/config/routes.js +1 -1
  4. package/dist/cjs/src/containerManager.d.ts +2 -1
  5. package/dist/cjs/src/containerManager.js +125 -21
  6. package/dist/cjs/src/definitionsManager.d.ts +1 -0
  7. package/dist/cjs/src/definitionsManager.js +7 -4
  8. package/dist/cjs/src/instanceManager.d.ts +12 -2
  9. package/dist/cjs/src/instanceManager.js +253 -200
  10. package/dist/cjs/src/operatorManager.d.ts +2 -0
  11. package/dist/cjs/src/operatorManager.js +69 -67
  12. package/dist/cjs/src/socketManager.d.ts +1 -0
  13. package/dist/cjs/src/socketManager.js +3 -0
  14. package/dist/cjs/src/types.d.ts +1 -0
  15. package/dist/cjs/src/utils/BlockInstanceRunner.js +2 -2
  16. package/dist/esm/src/config/routes.js +1 -1
  17. package/dist/esm/src/containerManager.d.ts +2 -1
  18. package/dist/esm/src/containerManager.js +126 -22
  19. package/dist/esm/src/definitionsManager.d.ts +1 -0
  20. package/dist/esm/src/definitionsManager.js +8 -5
  21. package/dist/esm/src/instanceManager.d.ts +12 -2
  22. package/dist/esm/src/instanceManager.js +253 -200
  23. package/dist/esm/src/operatorManager.d.ts +2 -0
  24. package/dist/esm/src/operatorManager.js +67 -65
  25. package/dist/esm/src/socketManager.d.ts +1 -0
  26. package/dist/esm/src/socketManager.js +3 -0
  27. package/dist/esm/src/types.d.ts +1 -0
  28. package/dist/esm/src/utils/BlockInstanceRunner.js +2 -2
  29. package/dist/esm/src/utils/utils.js +1 -1
  30. package/package.json +3 -1
  31. package/src/config/routes.ts +1 -1
  32. package/src/containerManager.ts +178 -43
  33. package/src/definitionsManager.ts +9 -5
  34. package/src/instanceManager.ts +288 -228
  35. package/src/instances/routes.ts +1 -1
  36. package/src/operatorManager.ts +72 -70
  37. package/src/socketManager.ts +4 -0
  38. package/src/types.ts +1 -1
  39. package/src/utils/BlockInstanceRunner.ts +12 -22
  40. 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
- const KIND_OPERATOR = 'core/resource-type-operator';
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
- const operator = this.getOperator(resourceType, version);
111
- const operatorData = operator.getData();
112
- const portTypes = Object.keys(operatorData.ports);
113
- portTypes.sort();
114
- const ports = {};
115
- for (let i = 0; i < portTypes.length; i++) {
116
- const portType = portTypes[i];
117
- let containerPortInfo = operatorData.ports[portType];
118
- const hostPort = await serviceManager.ensureServicePort(systemId, resourceType, portType);
119
- if (typeof containerPortInfo === 'number' || typeof containerPortInfo === 'string') {
120
- containerPortInfo = { port: containerPortInfo, type: 'tcp' };
121
- }
122
- if (!containerPortInfo.type) {
123
- containerPortInfo.type = 'tcp';
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 portId = containerPortInfo.port + '/' + containerPortInfo.type;
126
- ports[portId] = {
127
- type: portType,
128
- hostPort,
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
- const mounts = await containerManager.createMounts(systemId, resourceType, operatorData.mounts);
132
- const nameParts = [
133
- systemId,
134
- resourceType.toLowerCase(),
135
- version
136
- ];
137
- const containerName = `kapeta-resource-${md5(nameParts.join('_'))}`;
138
- const PortBindings = {};
139
- const Env = [];
140
- const Labels = {
141
- kapeta: 'true',
142
- };
143
- const bindHost = getBindHost();
144
- const ExposedPorts = {};
145
- _.forEach(ports, (portInfo, containerPort) => {
146
- ExposedPorts['' + containerPort] = {};
147
- PortBindings['' + containerPort] = [
148
- {
149
- HostPort: '' + portInfo.hostPort,
150
- HostIp: bindHost,
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
- Labels[CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
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
  }
@@ -58,6 +58,7 @@ export type InstanceInfo = {
58
58
  type: InstanceType;
59
59
  owner: InstanceOwner;
60
60
  status: InstanceStatus;
61
+ errorMessage?: string;
61
62
  desiredStatus: DesiredInstanceStatus;
62
63
  address?: string;
63
64
  startedAt?: number;
@@ -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 "md5";
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.11.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",
@@ -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.restart(req.kapeta!.systemId, req.kapeta!.instanceId);
44
+ await instanceManager.prepareForRestart(req.kapeta!.systemId, req.kapeta!.instanceId);
45
45
  } else {
46
46
  configManager.setConfigForSystem(req.kapeta!.systemId, config);
47
47
  }
@@ -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 "./utils/utils";
13
- import {InstanceInfo, LogEntry, LogSource} from "./types";
14
- import EventEmitter from "events";
15
- import {LogData} from "./utils/LogData";
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(this._mountDir,
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, mountName);
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
- console.log('Pulling image: %s', image);
242
- const stream = await this.docker()
243
- .image.create(
244
- {},
245
- {
246
- fromImage: imageName,
247
- tag: tag,
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
- ) as ReadStream;
379
+ });
250
380
 
251
- await promisifyStream(stream, (chunk) => {
252
- console.log('Data from docker: "%s"', chunk.toString());
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
- console.log('Image pulled: %s', image);
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 = false;
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
- source: "stdout",
457
- level: "ERROR",
458
- time: Date.now(),
459
- message: "Container not found"
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
- }) as ReadStream;
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 "@kapeta/nodejs-utils";
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
- const uri = parseKapetaUri(ref);
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();