@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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/src/assetManager.d.ts +2 -2
- package/dist/cjs/src/assetManager.js +16 -16
- package/dist/cjs/src/assets/routes.js +2 -2
- package/dist/cjs/src/authManager.d.ts +12 -0
- package/dist/cjs/src/authManager.js +60 -0
- package/dist/cjs/src/codeGeneratorManager.d.ts +1 -1
- package/dist/cjs/src/codeGeneratorManager.js +3 -3
- package/dist/cjs/src/configManager.js +2 -2
- package/dist/cjs/src/containerManager.d.ts +15 -0
- package/dist/cjs/src/containerManager.js +190 -37
- package/dist/cjs/src/definitionsManager.d.ts +7 -6
- package/dist/cjs/src/definitionsManager.js +102 -18
- package/dist/cjs/src/instanceManager.d.ts +1 -1
- package/dist/cjs/src/instanceManager.js +7 -12
- package/dist/cjs/src/instances/routes.js +2 -2
- package/dist/cjs/src/operatorManager.d.ts +1 -1
- package/dist/cjs/src/operatorManager.js +7 -9
- package/dist/cjs/src/providerManager.d.ts +2 -1
- package/dist/cjs/src/providerManager.js +23 -15
- package/dist/cjs/src/repositoryManager.d.ts +2 -2
- package/dist/cjs/src/repositoryManager.js +8 -9
- package/dist/cjs/src/socketManager.d.ts +2 -2
- package/dist/cjs/src/socketManager.js +39 -14
- package/dist/cjs/src/utils/BlockInstanceRunner.js +6 -8
- package/dist/esm/index.js +4 -2
- package/dist/esm/src/assetManager.d.ts +2 -2
- package/dist/esm/src/assetManager.js +16 -16
- package/dist/esm/src/assets/routes.js +2 -2
- package/dist/esm/src/authManager.d.ts +12 -0
- package/dist/esm/src/authManager.js +60 -0
- package/dist/esm/src/codeGeneratorManager.d.ts +1 -1
- package/dist/esm/src/codeGeneratorManager.js +3 -3
- package/dist/esm/src/configManager.js +2 -2
- package/dist/esm/src/containerManager.d.ts +15 -0
- package/dist/esm/src/containerManager.js +190 -37
- package/dist/esm/src/definitionsManager.d.ts +7 -6
- package/dist/esm/src/definitionsManager.js +102 -18
- package/dist/esm/src/instanceManager.d.ts +1 -1
- package/dist/esm/src/instanceManager.js +7 -12
- package/dist/esm/src/instances/routes.js +2 -2
- package/dist/esm/src/operatorManager.d.ts +1 -1
- package/dist/esm/src/operatorManager.js +7 -9
- package/dist/esm/src/providerManager.d.ts +2 -1
- package/dist/esm/src/providerManager.js +23 -15
- package/dist/esm/src/repositoryManager.d.ts +2 -2
- package/dist/esm/src/repositoryManager.js +8 -9
- package/dist/esm/src/socketManager.d.ts +2 -2
- package/dist/esm/src/socketManager.js +39 -14
- package/dist/esm/src/utils/BlockInstanceRunner.js +6 -8
- package/index.ts +4 -2
- package/package.json +1 -1
- package/src/assetManager.ts +18 -16
- package/src/assets/routes.ts +2 -2
- package/src/authManager.ts +62 -0
- package/src/codeGeneratorManager.ts +3 -3
- package/src/configManager.ts +2 -2
- package/src/containerManager.ts +210 -40
- package/src/definitionsManager.ts +132 -17
- package/src/instanceManager.ts +7 -14
- package/src/instances/routes.ts +2 -2
- package/src/operatorManager.ts +7 -12
- package/src/providerManager.ts +27 -19
- package/src/repositoryManager.ts +8 -11
- package/src/socketManager.ts +42 -15
- 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
|
-
|
86
|
-
|
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 (
|
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
|
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
|
-
|
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
|
752
|
+
const chunks = [];
|
567
753
|
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
|
-
}
|
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
|
4
|
-
private
|
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;
|