@kapeta/local-cluster-service 0.17.0 → 0.19.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 (67) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/index.js +4 -2
  3. package/dist/cjs/src/assetManager.d.ts +2 -2
  4. package/dist/cjs/src/assetManager.js +16 -16
  5. package/dist/cjs/src/assets/routes.js +2 -2
  6. package/dist/cjs/src/authManager.d.ts +12 -0
  7. package/dist/cjs/src/authManager.js +60 -0
  8. package/dist/cjs/src/codeGeneratorManager.d.ts +1 -1
  9. package/dist/cjs/src/codeGeneratorManager.js +3 -3
  10. package/dist/cjs/src/configManager.js +2 -2
  11. package/dist/cjs/src/containerManager.d.ts +15 -0
  12. package/dist/cjs/src/containerManager.js +190 -37
  13. package/dist/cjs/src/definitionsManager.d.ts +7 -6
  14. package/dist/cjs/src/definitionsManager.js +102 -18
  15. package/dist/cjs/src/instanceManager.d.ts +1 -1
  16. package/dist/cjs/src/instanceManager.js +7 -12
  17. package/dist/cjs/src/instances/routes.js +2 -2
  18. package/dist/cjs/src/operatorManager.d.ts +1 -1
  19. package/dist/cjs/src/operatorManager.js +7 -9
  20. package/dist/cjs/src/providerManager.d.ts +2 -1
  21. package/dist/cjs/src/providerManager.js +23 -15
  22. package/dist/cjs/src/repositoryManager.d.ts +2 -2
  23. package/dist/cjs/src/repositoryManager.js +8 -9
  24. package/dist/cjs/src/socketManager.d.ts +2 -2
  25. package/dist/cjs/src/socketManager.js +39 -14
  26. package/dist/cjs/src/utils/BlockInstanceRunner.js +6 -8
  27. package/dist/esm/index.js +4 -2
  28. package/dist/esm/src/assetManager.d.ts +2 -2
  29. package/dist/esm/src/assetManager.js +16 -16
  30. package/dist/esm/src/assets/routes.js +2 -2
  31. package/dist/esm/src/authManager.d.ts +12 -0
  32. package/dist/esm/src/authManager.js +60 -0
  33. package/dist/esm/src/codeGeneratorManager.d.ts +1 -1
  34. package/dist/esm/src/codeGeneratorManager.js +3 -3
  35. package/dist/esm/src/configManager.js +2 -2
  36. package/dist/esm/src/containerManager.d.ts +15 -0
  37. package/dist/esm/src/containerManager.js +190 -37
  38. package/dist/esm/src/definitionsManager.d.ts +7 -6
  39. package/dist/esm/src/definitionsManager.js +102 -18
  40. package/dist/esm/src/instanceManager.d.ts +1 -1
  41. package/dist/esm/src/instanceManager.js +7 -12
  42. package/dist/esm/src/instances/routes.js +2 -2
  43. package/dist/esm/src/operatorManager.d.ts +1 -1
  44. package/dist/esm/src/operatorManager.js +7 -9
  45. package/dist/esm/src/providerManager.d.ts +2 -1
  46. package/dist/esm/src/providerManager.js +23 -15
  47. package/dist/esm/src/repositoryManager.d.ts +2 -2
  48. package/dist/esm/src/repositoryManager.js +8 -9
  49. package/dist/esm/src/socketManager.d.ts +2 -2
  50. package/dist/esm/src/socketManager.js +39 -14
  51. package/dist/esm/src/utils/BlockInstanceRunner.js +6 -8
  52. package/index.ts +4 -2
  53. package/package.json +1 -1
  54. package/src/assetManager.ts +18 -16
  55. package/src/assets/routes.ts +2 -2
  56. package/src/authManager.ts +62 -0
  57. package/src/codeGeneratorManager.ts +3 -3
  58. package/src/configManager.ts +2 -2
  59. package/src/containerManager.ts +210 -40
  60. package/src/definitionsManager.ts +132 -17
  61. package/src/instanceManager.ts +7 -14
  62. package/src/instances/routes.ts +2 -2
  63. package/src/operatorManager.ts +7 -12
  64. package/src/providerManager.ts +27 -19
  65. package/src/repositoryManager.ts +8 -11
  66. package/src/socketManager.ts +42 -15
  67. package/src/utils/BlockInstanceRunner.ts +6 -8
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [0.19.0](https://github.com/kapetacom/local-cluster-service/compare/v0.18.0...v0.19.0) (2023-09-03)
2
+
3
+
4
+ ### Features
5
+
6
+ * 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))
7
+
8
+ # [0.18.0](https://github.com/kapetacom/local-cluster-service/compare/v0.17.0...v0.18.0) (2023-09-02)
9
+
10
+
11
+ ### Features
12
+
13
+ * Auto-rename sample plan when available ([#66](https://github.com/kapetacom/local-cluster-service/issues/66)) ([d95b844](https://github.com/kapetacom/local-cluster-service/commit/d95b844baff7e5bfceb354c854526881051f2308))
14
+
1
15
  # [0.17.0](https://github.com/kapetacom/local-cluster-service/compare/v0.16.8...v0.17.0) (2023-09-02)
2
16
 
3
17
 
package/dist/cjs/index.js CHANGED
@@ -27,6 +27,7 @@ const request_1 = __importDefault(require("request"));
27
27
  const repositoryManager_1 = require("./src/repositoryManager");
28
28
  const commandLineUtils_1 = require("./src/utils/commandLineUtils");
29
29
  const DefaultProviderInstaller_1 = require("./src/utils/DefaultProviderInstaller");
30
+ const authManager_1 = require("./src/authManager");
30
31
  let currentServer = null;
31
32
  function createServer() {
32
33
  const app = (0, express_1.default)();
@@ -127,6 +128,7 @@ exports.default = {
127
128
  throw new Error(`Cluster service already running on: ${clusterHost}:${clusterPort}.`);
128
129
  }
129
130
  await clusterService_1.clusterService.init();
131
+ await authManager_1.authManager.listenForChanges();
130
132
  currentServer = createServer();
131
133
  const port = clusterService_1.clusterService.getClusterServicePort();
132
134
  const host = clusterService_1.clusterService.getClusterServiceHost();
@@ -149,7 +151,7 @@ exports.default = {
149
151
  reject(err);
150
152
  });
151
153
  const bindHost = (0, utils_1.getBindHost)(host);
152
- currentServer.listen(port, bindHost, () => {
154
+ currentServer.listen(port, bindHost, async () => {
153
155
  try {
154
156
  (0, commandLineUtils_1.ensureCLI)().catch((e) => console.error('Failed to install CLI.', e));
155
157
  }
@@ -158,7 +160,7 @@ exports.default = {
158
160
  }
159
161
  try {
160
162
  // Start installation process for all default providers
161
- repositoryManager_1.repositoryManager.ensureDefaultProviders();
163
+ await repositoryManager_1.repositoryManager.ensureDefaultProviders();
162
164
  }
163
165
  catch (e) {
164
166
  console.error('Failed to install default providers.', e);
@@ -17,8 +17,8 @@ declare class AssetManager {
17
17
  * @param {string[]} [assetKinds]
18
18
  * @returns {{path: *, ref: string, data: *, editable: boolean, kind: *, exists: boolean}[]}
19
19
  */
20
- getAssets(assetKinds?: string[]): EnrichedAsset[];
21
- getPlans(): EnrichedAsset[];
20
+ getAssets(assetKinds?: string[]): Promise<EnrichedAsset[]>;
21
+ getPlans(): Promise<EnrichedAsset[]>;
22
22
  getPlan(ref: string, noCache?: boolean): Promise<Definition>;
23
23
  getAsset(ref: string, noCache?: boolean, autoFetch?: boolean): Promise<EnrichedAsset | undefined>;
24
24
  createAsset(path: string, yaml: BlockDefinition, sourceOfChange?: SourceOfChange): Promise<EnrichedAsset[]>;
@@ -48,9 +48,9 @@ class AssetManager {
48
48
  * @param {string[]} [assetKinds]
49
49
  * @returns {{path: *, ref: string, data: *, editable: boolean, kind: *, exists: boolean}[]}
50
50
  */
51
- getAssets(assetKinds) {
51
+ async getAssets(assetKinds) {
52
52
  if (!assetKinds) {
53
- const blockTypeProviders = definitionsManager_1.definitionsManager.getDefinitions([
53
+ const blockTypeProviders = await definitionsManager_1.definitionsManager.getDefinitions([
54
54
  'core/block-type',
55
55
  'core/block-type-operator',
56
56
  ]);
@@ -59,10 +59,10 @@ class AssetManager {
59
59
  });
60
60
  assetKinds.push('core/plan');
61
61
  }
62
- const assets = definitionsManager_1.definitionsManager.getDefinitions(assetKinds);
62
+ const assets = await definitionsManager_1.definitionsManager.getDefinitions(assetKinds);
63
63
  return assets.map(enrichAsset);
64
64
  }
65
- getPlans() {
65
+ async getPlans() {
66
66
  return this.getAssets(['core/plan']);
67
67
  }
68
68
  async getPlan(ref, noCache = false) {
@@ -82,17 +82,16 @@ class AssetManager {
82
82
  if (autoFetch) {
83
83
  await repositoryManager_1.repositoryManager.ensureAsset(uri.handle, uri.name, uri.version, true);
84
84
  }
85
- let asset = definitionsManager_1.definitionsManager
86
- .getDefinitions()
87
- .map(enrichAsset)
88
- .find((a) => (0, nodejs_utils_1.parseKapetaUri)(a.ref).equals(uri));
89
- if (autoFetch && !asset) {
85
+ const definitionInfo = await definitionsManager_1.definitionsManager.getDefinition(ref);
86
+ if (autoFetch && !definitionInfo) {
90
87
  throw new Error('Asset not found: ' + ref);
91
88
  }
92
- if (asset) {
89
+ if (definitionInfo) {
90
+ const asset = enrichAsset(definitionInfo);
93
91
  cacheManager_1.cacheManager.set(cacheKey, asset, CACHE_TTL);
92
+ return asset;
94
93
  }
95
- return asset;
94
+ return undefined;
96
95
  }
97
96
  async createAsset(path, yaml, sourceOfChange = 'filesystem') {
98
97
  if (await fs_extra_1.default.pathExists(path)) {
@@ -113,7 +112,7 @@ class AssetManager {
113
112
  definitionsManager_1.definitionsManager.clearCache();
114
113
  console.log(`Created asset at: ${path}`);
115
114
  const ref = `kapeta://${yaml.metadata.name}:local`;
116
- this.maybeGenerateCode(ref, path, yaml);
115
+ await this.maybeGenerateCode(ref, path, yaml);
117
116
  return asset;
118
117
  }
119
118
  async updateAsset(ref, yaml, sourceOfChange = 'filesystem') {
@@ -133,7 +132,7 @@ class AssetManager {
133
132
  console.log(`Updated asset at: ${asset.ymlPath}`);
134
133
  cacheManager_1.cacheManager.remove(toKey(ref));
135
134
  definitionsManager_1.definitionsManager.clearCache();
136
- this.maybeGenerateCode(asset.ref, asset.ymlPath, yaml);
135
+ await this.maybeGenerateCode(asset.ref, asset.ymlPath, yaml);
137
136
  }
138
137
  async importFile(filePath) {
139
138
  if (filePath.startsWith('file://')) {
@@ -152,7 +151,8 @@ class AssetManager {
152
151
  cacheManager_1.cacheManager.remove(key);
153
152
  });
154
153
  definitionsManager_1.definitionsManager.clearCache();
155
- return this.getAssets().filter((a) => refs.some((ref) => compareRefs(ref, a.ref)));
154
+ const assets = await this.getAssets();
155
+ return assets.filter((a) => refs.some((ref) => compareRefs(ref, a.ref)));
156
156
  }
157
157
  async unregisterAsset(ref) {
158
158
  const asset = await this.getAsset(ref, true);
@@ -176,9 +176,9 @@ class AssetManager {
176
176
  definitionsManager_1.definitionsManager.clearCache();
177
177
  return await repositoryManager_1.repositoryManager.ensureAsset(uri.handle, uri.name, uri.version, false);
178
178
  }
179
- maybeGenerateCode(ref, ymlPath, block) {
179
+ async maybeGenerateCode(ref, ymlPath, block) {
180
180
  ref = (0, utils_1.normalizeKapetaUri)(ref);
181
- if (codeGeneratorManager_1.codeGeneratorManager.canGenerateCode(block)) {
181
+ if (await codeGeneratorManager_1.codeGeneratorManager.canGenerateCode(block)) {
182
182
  const assetTitle = block.metadata.title ? block.metadata.title : (0, nodejs_utils_1.parseKapetaUri)(block.metadata.name).name;
183
183
  taskManager_1.taskManager.add(`codegen:${ref}`, async () => {
184
184
  await codeGeneratorManager_1.codeGeneratorManager.generate(ymlPath, block);
@@ -28,8 +28,8 @@ router.use('/', stringBody_1.stringBody);
28
28
  /**
29
29
  * Get all local assets available
30
30
  */
31
- router.get('/', (req, res) => {
32
- res.send(assetManager_1.assetManager.getAssets([]));
31
+ router.get('/', async (req, res) => {
32
+ res.send(await assetManager_1.assetManager.getAssets([]));
33
33
  });
34
34
  /**
35
35
  * Get single asset
@@ -0,0 +1,12 @@
1
+ /// <reference types="node" />
2
+ import { EventEmitter } from 'node:events';
3
+ declare class AuthManager extends EventEmitter {
4
+ private watcher?;
5
+ private hadToken;
6
+ constructor();
7
+ listenForChanges(): void;
8
+ private hasToken;
9
+ private handleFileChange;
10
+ }
11
+ export declare const authManager: AuthManager;
12
+ export {};
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.authManager = void 0;
7
+ const node_events_1 = require("node:events");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const chokidar_1 = __importDefault(require("chokidar"));
10
+ const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
11
+ const definitionsManager_1 = require("./definitionsManager");
12
+ const nodejs_api_client_1 = require("@kapeta/nodejs-api-client");
13
+ const socketManager_1 = require("./socketManager");
14
+ class AuthManager extends node_events_1.EventEmitter {
15
+ watcher;
16
+ hadToken;
17
+ constructor() {
18
+ super();
19
+ this.hadToken = this.hasToken();
20
+ }
21
+ listenForChanges() {
22
+ const parentDir = node_path_1.default.dirname(local_cluster_config_1.default.getKapetaBasedir());
23
+ //We watch the parent dir to catch changes to the base dir itself
24
+ this.watcher = chokidar_1.default.watch(parentDir, {
25
+ followSymlinks: false,
26
+ ignorePermissionErrors: true,
27
+ disableGlobbing: true,
28
+ persistent: true,
29
+ ignoreInitial: true,
30
+ depth: 1,
31
+ ignored: (path) => {
32
+ return !path.startsWith(local_cluster_config_1.default.getKapetaBasedir());
33
+ },
34
+ });
35
+ this.watcher.add(local_cluster_config_1.default.getKapetaBasedir());
36
+ this.watcher.on('all', this.handleFileChange.bind(this));
37
+ this.watcher.on('error', (error) => {
38
+ console.log('Error watching repository', error);
39
+ });
40
+ this.watcher.on('ready', () => {
41
+ console.log('Watching for auth changes: %s', local_cluster_config_1.default.getKapetaBasedir());
42
+ });
43
+ }
44
+ hasToken() {
45
+ const api = new nodejs_api_client_1.KapetaAPI();
46
+ return api.hasToken();
47
+ }
48
+ async handleFileChange(eventName, path) {
49
+ const hasTokenNow = this.hasToken();
50
+ if (this.hadToken !== hasTokenNow) {
51
+ socketManager_1.socketManager.emitGlobal('auth-change', {});
52
+ if (hasTokenNow) {
53
+ // Clear the cache in case we need to rewrite the sample plan
54
+ definitionsManager_1.definitionsManager.clearCache();
55
+ }
56
+ this.hadToken = hasTokenNow;
57
+ }
58
+ }
59
+ }
60
+ exports.authManager = new AuthManager();
@@ -1,7 +1,7 @@
1
1
  import { BlockDefinition } from '@kapeta/schemas';
2
2
  declare class CodeGeneratorManager {
3
3
  reload(): Promise<void>;
4
- canGenerateCode(yamlContent: BlockDefinition): boolean;
4
+ canGenerateCode(yamlContent: BlockDefinition): Promise<boolean>;
5
5
  generate(yamlFile: string, yamlContent: BlockDefinition): Promise<void>;
6
6
  }
7
7
  export declare const codeGeneratorManager: CodeGeneratorManager;
@@ -12,7 +12,7 @@ const BLOCK_TYPE_KIND = 'core/block-type';
12
12
  class CodeGeneratorManager {
13
13
  async reload() {
14
14
  codegen_1.registry.reset();
15
- const languageTargets = definitionsManager_1.definitionsManager.getDefinitions(TARGET_KIND);
15
+ const languageTargets = await definitionsManager_1.definitionsManager.getDefinitions(TARGET_KIND);
16
16
  for (const languageTarget of languageTargets) {
17
17
  const key = `${languageTarget.definition.metadata.name}:${languageTarget.version}`;
18
18
  try {
@@ -29,12 +29,12 @@ class CodeGeneratorManager {
29
29
  }
30
30
  }
31
31
  }
32
- canGenerateCode(yamlContent) {
32
+ async canGenerateCode(yamlContent) {
33
33
  if (!yamlContent.spec.target?.kind) {
34
34
  //Not all block types have targets
35
35
  return false;
36
36
  }
37
- const blockTypes = definitionsManager_1.definitionsManager.getDefinitions(BLOCK_TYPE_KIND);
37
+ const blockTypes = await definitionsManager_1.definitionsManager.getDefinitions(BLOCK_TYPE_KIND);
38
38
  const blockTypeKinds = blockTypes.map((blockType) => blockType.definition.metadata.name.toLowerCase() + ':' + blockType.version);
39
39
  return !!(yamlContent && yamlContent.kind && blockTypeKinds.indexOf(yamlContent.kind.toLowerCase()) > -1);
40
40
  }
@@ -61,7 +61,7 @@ class ConfigManager {
61
61
  if (systemId) {
62
62
  systemId = (0, utils_1.normalizeKapetaUri)(systemId);
63
63
  }
64
- const planAssets = assetManager_1.assetManager.getPlans();
64
+ const planAssets = await assetManager_1.assetManager.getPlans();
65
65
  const blockUri = (0, nodejs_utils_1.parseKapetaUri)(blockRef);
66
66
  let matchingIdentities = [];
67
67
  planAssets.forEach((planAsset) => {
@@ -99,7 +99,7 @@ class ConfigManager {
99
99
  async verifyIdentity(blockRef, systemId, instanceId) {
100
100
  blockRef = (0, utils_1.normalizeKapetaUri)(blockRef);
101
101
  systemId = (0, utils_1.normalizeKapetaUri)(systemId);
102
- const planAssets = assetManager_1.assetManager.getPlans();
102
+ const planAssets = await assetManager_1.assetManager.getPlans();
103
103
  const systemUri = systemId ? (0, nodejs_utils_1.parseKapetaUri)(systemId) : null;
104
104
  const blockUri = (0, nodejs_utils_1.parseKapetaUri)(blockRef);
105
105
  let found = false;
@@ -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,175 @@ 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
+ console.log('Stopped listening for logs on container: %s', containerName);
484
+ try {
485
+ const stream = this.logStreams[containerName].stream;
486
+ if (stream) {
487
+ await stream.close();
488
+ }
489
+ }
490
+ catch (err) {
491
+ // Ignore
492
+ }
493
+ delete this.logStreams[containerName];
494
+ }
495
+ }
496
+ async ensureLogListening(systemId, instanceId, handler) {
497
+ const containerName = (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
498
+ try {
499
+ if (this.logStreams[containerName]?.stream) {
500
+ // Already listening - will shut itself down
501
+ return;
502
+ }
503
+ if (this.logStreams[containerName]?.timer) {
504
+ clearTimeout(this.logStreams[containerName].timer);
505
+ }
506
+ const tryLater = () => {
507
+ this.logStreams[containerName] = {
508
+ timer: setTimeout(() => {
509
+ // Keep trying until user decides to not listen anymore
510
+ this.ensureLogListening(systemId, instanceId, handler);
511
+ }, 5000),
512
+ };
513
+ };
514
+ const containerInfo = await this.getContainerByName(containerName);
515
+ if (!containerInfo || !(await containerInfo.isRunning())) {
516
+ // Container not currently running - try again in 5 seconds
517
+ tryLater();
518
+ return;
519
+ }
520
+ const stream = await containerInfo.getLogStream();
521
+ stream.onLog((log) => {
522
+ try {
523
+ handler(log);
524
+ }
525
+ catch (err) {
526
+ console.warn('Error handling log', err);
527
+ }
528
+ });
529
+ stream.onEnd(() => {
530
+ // We get here if the container is stopped
531
+ delete this.logStreams[containerName];
532
+ tryLater();
533
+ });
534
+ stream.onError((err) => {
535
+ // We get here if the container crashes
536
+ delete this.logStreams[containerName];
537
+ tryLater();
538
+ });
539
+ this.logStreams[containerName] = {
540
+ stream,
541
+ };
542
+ }
543
+ catch (err) {
544
+ // Ignore
545
+ }
546
+ }
547
+ }
548
+ function readLogBuffer(logBuffer) {
549
+ const out = [];
550
+ let offset = 0;
551
+ while (offset < logBuffer.length) {
552
+ try {
553
+ // Read the docker log format - explained here:
554
+ // https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach
555
+ // or here : https://ahmet.im/blog/docker-logs-api-binary-format-explained/
556
+ // First byte is stream type
557
+ const streamTypeInt = logBuffer.readInt8(offset);
558
+ const streamType = streamTypeInt === 1 ? 'stdout' : 'stderr';
559
+ if (streamTypeInt !== 1 && streamTypeInt !== 2) {
560
+ console.error('Unknown stream type: %s', streamTypeInt, out[out.length - 1]);
561
+ break;
562
+ }
563
+ // Bytes 4-8 is frame size
564
+ const messageLength = logBuffer.readInt32BE(offset + 4);
565
+ // After that is the message - with the message length
566
+ const dataWithoutStreamType = logBuffer.subarray(offset + 8, offset + 8 + messageLength);
567
+ const raw = dataWithoutStreamType.toString();
568
+ // Split the message into date and message
569
+ const firstSpaceIx = raw.indexOf(' ');
570
+ const dateString = raw.substring(0, firstSpaceIx);
571
+ const line = raw.substring(firstSpaceIx + 1);
572
+ offset = offset + messageLength + 8;
573
+ if (!dateString) {
574
+ break;
575
+ }
576
+ out.push({
577
+ time: new Date(dateString).getTime(),
578
+ message: line,
579
+ level: 'INFO',
580
+ source: streamType,
581
+ });
582
+ }
583
+ catch (err) {
584
+ console.error('Error parsing log entry', err);
585
+ offset = logBuffer.length;
586
+ break;
587
+ }
588
+ }
589
+ return out;
590
+ }
591
+ class ClosableLogStream {
592
+ stream;
593
+ eventEmitter;
594
+ constructor(stream) {
595
+ this.stream = stream;
596
+ this.eventEmitter = new node_events_1.EventEmitter();
597
+ stream.on('data', (data) => {
598
+ const logs = readLogBuffer(data);
599
+ logs.forEach((log) => {
600
+ this.eventEmitter.emit('log', log);
601
+ });
602
+ });
603
+ stream.on('end', () => {
604
+ this.eventEmitter.emit('end');
605
+ });
606
+ stream.on('error', (error) => {
607
+ this.eventEmitter.emit('error', error);
608
+ });
609
+ stream.on('close', () => {
610
+ this.eventEmitter.emit('end');
611
+ });
612
+ }
613
+ onLog(listener) {
614
+ this.eventEmitter.on('log', listener);
615
+ return () => {
616
+ this.eventEmitter.removeListener('log', listener);
617
+ };
618
+ }
619
+ onEnd(listener) {
620
+ this.eventEmitter.on('end', listener);
621
+ return () => {
622
+ this.eventEmitter.removeListener('end', listener);
623
+ };
624
+ }
625
+ onError(listener) {
626
+ this.eventEmitter.on('error', listener);
627
+ return () => {
628
+ this.eventEmitter.removeListener('error', listener);
629
+ };
630
+ }
631
+ close() {
632
+ return new Promise((resolve, reject) => {
633
+ try {
634
+ this.stream.close((err) => {
635
+ if (err) {
636
+ console.warn('Error closing log stream', err);
637
+ }
638
+ resolve();
639
+ });
640
+ }
641
+ catch (err) {
642
+ // Ignore
643
+ }
644
+ });
645
+ }
475
646
  }
476
647
  class ContainerInfo {
477
648
  _container;
@@ -555,52 +726,34 @@ class ContainerInfo {
555
726
  });
556
727
  return ports;
557
728
  }
729
+ async getLogStream() {
730
+ try {
731
+ const logStream = (await this.native.logs({
732
+ stdout: true,
733
+ stderr: true,
734
+ follow: true,
735
+ tail: 0,
736
+ timestamps: true,
737
+ }));
738
+ return new ClosableLogStream(logStream);
739
+ }
740
+ catch (err) {
741
+ console.log('Error getting log stream', err);
742
+ throw err;
743
+ }
744
+ }
558
745
  async getLogs() {
559
746
  const logStream = (await this.native.logs({
560
747
  stdout: true,
561
748
  stderr: true,
562
749
  follow: false,
563
- tail: 100,
564
750
  timestamps: true,
565
751
  }));
566
- const out = [];
752
+ const chunks = [];
567
753
  await promisifyStream(logStream, (data) => {
568
- const buf = data;
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
- }
754
+ chunks.push(data);
603
755
  });
756
+ const out = readLogBuffer(Buffer.concat(chunks));
604
757
  if (out.length === 0) {
605
758
  out.push({
606
759
  time: Date.now(),
@@ -1,11 +1,12 @@
1
1
  import { DefinitionInfo } from '@kapeta/local-cluster-config';
2
+ export declare const SAMPLE_PLAN_NAME = "kapeta/sample-nodejs-plan";
2
3
  declare class DefinitionsManager {
3
- private getHash;
4
- private getFullKey;
5
- getDefinitions(kindFilter?: string | string[]): DefinitionInfo[];
6
- exists(ref: string): boolean;
7
- getProviderDefinitions(): DefinitionInfo[];
8
- getDefinition(ref: string): DefinitionInfo | undefined;
4
+ private resolveDefinitionsAndSamples;
5
+ private applyFilters;
6
+ getDefinitions(kindFilter?: string | string[]): Promise<DefinitionInfo[]>;
7
+ exists(ref: string): Promise<boolean>;
8
+ getProviderDefinitions(): Promise<DefinitionInfo[]>;
9
+ getDefinition(ref: string): Promise<DefinitionInfo | undefined>;
9
10
  clearCache(): void;
10
11
  }
11
12
  export declare const definitionsManager: DefinitionsManager;