@kapeta/local-cluster-service 0.15.2 → 0.16.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/src/RepositoryWatcher.d.ts +22 -0
- package/dist/cjs/src/RepositoryWatcher.js +273 -0
- package/dist/cjs/src/assetManager.js +33 -20
- package/dist/cjs/src/containerManager.js +0 -1
- package/dist/cjs/src/identities/routes.js +2 -1
- package/dist/cjs/src/instanceManager.d.ts +0 -2
- package/dist/cjs/src/instanceManager.js +16 -35
- package/dist/cjs/src/progressListener.d.ts +5 -6
- package/dist/cjs/src/progressListener.js +54 -36
- package/dist/cjs/src/repositoryManager.d.ts +4 -4
- package/dist/cjs/src/repositoryManager.js +20 -93
- package/dist/cjs/src/socketManager.d.ts +18 -6
- package/dist/cjs/src/socketManager.js +35 -1
- package/dist/esm/src/RepositoryWatcher.d.ts +22 -0
- package/dist/esm/src/RepositoryWatcher.js +266 -0
- package/dist/esm/src/assetManager.js +35 -22
- package/dist/esm/src/containerManager.js +0 -1
- package/dist/esm/src/identities/routes.js +2 -1
- package/dist/esm/src/instanceManager.d.ts +0 -2
- package/dist/esm/src/instanceManager.js +16 -35
- package/dist/esm/src/progressListener.d.ts +5 -6
- package/dist/esm/src/progressListener.js +53 -36
- package/dist/esm/src/repositoryManager.d.ts +4 -4
- package/dist/esm/src/repositoryManager.js +20 -93
- package/dist/esm/src/socketManager.d.ts +18 -6
- package/dist/esm/src/socketManager.js +34 -0
- package/package.json +2 -2
- package/src/RepositoryWatcher.ts +304 -0
- package/src/assetManager.ts +39 -25
- package/src/containerManager.ts +0 -5
- package/src/identities/routes.ts +2 -1
- package/src/instanceManager.ts +22 -35
- package/src/progressListener.ts +59 -39
- package/src/repositoryManager.ts +26 -100
- package/src/socketManager.ts +44 -5
package/src/progressListener.ts
CHANGED
@@ -1,19 +1,38 @@
|
|
1
1
|
import { spawn } from '@kapeta/nodejs-process';
|
2
|
-
import {
|
3
|
-
|
4
|
-
|
2
|
+
import { socketManager } from './socketManager';
|
3
|
+
import { LogEntry } from './types';
|
4
|
+
import { format } from 'node:util';
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
export class ProgressListener {
|
7
|
+
private readonly systemId: string | undefined;
|
8
|
+
private readonly instanceId: string | undefined;
|
9
|
+
|
10
|
+
constructor(systemId?: string, instanceId?: string) {
|
11
|
+
this.systemId = systemId;
|
12
|
+
this.instanceId = instanceId;
|
8
13
|
}
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
private emitLog(payload: Omit<LogEntry, 'time' | 'source'>) {
|
16
|
+
const logEntry: LogEntry = {
|
17
|
+
...payload,
|
18
|
+
source: 'stdout',
|
19
|
+
time: Date.now(),
|
20
|
+
};
|
21
|
+
if (this.systemId && this.instanceId) {
|
22
|
+
socketManager.emitInstanceLog(this.systemId, this.instanceId, logEntry);
|
23
|
+
return;
|
24
|
+
}
|
25
|
+
|
26
|
+
if (this.systemId) {
|
27
|
+
socketManager.emitSystemLog(this.systemId, logEntry);
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
|
31
|
+
socketManager.emitGlobalLog(logEntry);
|
32
|
+
}
|
15
33
|
|
16
|
-
|
34
|
+
run(command: string, directory?: string): Promise<{ exit: number; signal: NodeJS.Signals | null; output: string }> {
|
35
|
+
this.info(`Running command "${command}"`);
|
17
36
|
|
18
37
|
return new Promise(async (resolve, reject) => {
|
19
38
|
try {
|
@@ -25,7 +44,10 @@ class ProgressListener {
|
|
25
44
|
});
|
26
45
|
|
27
46
|
child.onData((data) => {
|
28
|
-
this.
|
47
|
+
this.emitLog({
|
48
|
+
level: data.type === 'stdout' ? 'INFO' : 'WARN',
|
49
|
+
message: data.line,
|
50
|
+
});
|
29
51
|
});
|
30
52
|
|
31
53
|
if (child.process.stdout) {
|
@@ -36,25 +58,16 @@ class ProgressListener {
|
|
36
58
|
|
37
59
|
child.process.on('exit', (exit, signal) => {
|
38
60
|
if (exit !== 0) {
|
39
|
-
this.
|
40
|
-
type: 'info',
|
41
|
-
message: `"${command}" failed: "${exit}"`,
|
42
|
-
});
|
61
|
+
this.warn(`Command "${command}" failed: ${exit}`);
|
43
62
|
reject(new Error(`Command "${command}" exited with code ${exit}`));
|
44
63
|
} else {
|
45
|
-
this.
|
46
|
-
type: 'info',
|
47
|
-
message: `Command OK: "${command}"`,
|
48
|
-
});
|
64
|
+
this.info(`Command OK: "${command}"`);
|
49
65
|
resolve({ exit, signal, output: Buffer.concat(chunks).toString() });
|
50
66
|
}
|
51
67
|
});
|
52
68
|
|
53
69
|
child.process.on('error', (err) => {
|
54
|
-
this.
|
55
|
-
type: 'info',
|
56
|
-
message: `"${command}" failed: "${err.message}"`,
|
57
|
-
});
|
70
|
+
this.warn(`"${command}" failed: "${err.message}"`);
|
58
71
|
reject(err);
|
59
72
|
});
|
60
73
|
|
@@ -66,48 +79,55 @@ class ProgressListener {
|
|
66
79
|
}
|
67
80
|
|
68
81
|
async progress(label: string, callback: () => void | Promise<void>) {
|
69
|
-
this.
|
82
|
+
this.info(`${label}: started`);
|
70
83
|
try {
|
71
84
|
const result = await callback();
|
72
|
-
this.
|
85
|
+
this.info(`${label}: done`);
|
73
86
|
return result;
|
74
87
|
} catch (e: any) {
|
75
|
-
this.
|
76
|
-
type: 'info',
|
77
|
-
message: `${label}: failed. ${e.message}`,
|
78
|
-
});
|
88
|
+
this.warn(`${label}: failed. ${e.message}`);
|
79
89
|
throw e;
|
80
90
|
}
|
81
91
|
}
|
82
92
|
|
83
93
|
async check(message: string, ok: boolean | Promise<boolean> | (() => Promise<boolean>)) {
|
84
94
|
const wasOk = await ok;
|
85
|
-
this.
|
95
|
+
this.info(`${message}: ${wasOk}`);
|
86
96
|
}
|
87
97
|
|
88
98
|
start(label: string) {
|
89
|
-
this.
|
99
|
+
this.info(label);
|
90
100
|
}
|
91
101
|
|
92
102
|
showValue(label: string, value: any) {
|
93
|
-
this.
|
103
|
+
this.info(`${label}: ${value}`);
|
94
104
|
}
|
95
105
|
|
96
106
|
error(msg: string, ...args: any[]) {
|
97
|
-
this.
|
107
|
+
this.emitLog({
|
108
|
+
message: format(msg, args),
|
109
|
+
level: 'ERROR',
|
110
|
+
});
|
98
111
|
}
|
99
112
|
|
100
113
|
warn(msg: string, ...args: any[]) {
|
101
|
-
this.
|
114
|
+
this.emitLog({
|
115
|
+
message: format(msg, args),
|
116
|
+
level: 'WARN',
|
117
|
+
});
|
102
118
|
}
|
103
119
|
|
104
120
|
info(msg: string, ...args: any[]) {
|
105
|
-
this.
|
121
|
+
this.emitLog({
|
122
|
+
message: format(msg, args),
|
123
|
+
level: 'INFO',
|
124
|
+
});
|
106
125
|
}
|
107
126
|
|
108
127
|
debug(msg: string, ...args: any[]) {
|
109
|
-
this.
|
128
|
+
this.emitLog({
|
129
|
+
message: format(msg, args),
|
130
|
+
level: 'DEBUG',
|
131
|
+
});
|
110
132
|
}
|
111
133
|
}
|
112
|
-
|
113
|
-
export const progressListener = new ProgressListener(socketManager);
|
package/src/repositoryManager.ts
CHANGED
@@ -1,21 +1,22 @@
|
|
1
|
-
import FS from 'node:fs';
|
2
1
|
import os from 'node:os';
|
3
|
-
import Path from 'node:path';
|
4
|
-
import watch from 'recursive-watch';
|
5
|
-
import FSExtra from 'fs-extra';
|
6
|
-
import ClusterConfiguration from '@kapeta/local-cluster-config';
|
7
|
-
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
8
2
|
import { socketManager } from './socketManager';
|
9
|
-
import { progressListener } from './progressListener';
|
10
3
|
import { Dependency } from '@kapeta/schemas';
|
11
4
|
import { Actions, Config, RegistryService } from '@kapeta/nodejs-registry-utils';
|
12
5
|
import { definitionsManager } from './definitionsManager';
|
13
6
|
import { Task, taskManager } from './taskManager';
|
14
7
|
import { normalizeKapetaUri } from './utils/utils';
|
8
|
+
|
9
|
+
import { ProgressListener } from './progressListener';
|
10
|
+
import { RepositoryWatcher } from './RepositoryWatcher';
|
15
11
|
import { assetManager } from './assetManager';
|
16
12
|
|
17
|
-
|
18
|
-
|
13
|
+
function clearAllCaches() {
|
14
|
+
definitionsManager.clearCache();
|
15
|
+
assetManager.clearCache();
|
16
|
+
}
|
17
|
+
|
18
|
+
const EVENT_DEFAULT_PROVIDERS_START = 'default-providers-start';
|
19
|
+
const EVENT_DEFAULT_PROVIDERS_END = 'default-providers-end';
|
19
20
|
|
20
21
|
const DEFAULT_PROVIDERS = [
|
21
22
|
'kapeta/block-type-service',
|
@@ -35,107 +36,37 @@ const DEFAULT_PROVIDERS = [
|
|
35
36
|
const INSTALL_ATTEMPTED: { [p: string]: boolean } = {};
|
36
37
|
|
37
38
|
class RepositoryManager {
|
38
|
-
private changeEventsEnabled: boolean;
|
39
39
|
private _registryService: RegistryService;
|
40
40
|
private _cache: { [key: string]: boolean };
|
41
|
-
private watcher
|
41
|
+
private watcher: RepositoryWatcher;
|
42
42
|
|
43
43
|
constructor() {
|
44
|
-
this.changeEventsEnabled = true;
|
45
|
-
this.listenForChanges();
|
46
44
|
this._registryService = new RegistryService(Config.data.registry.url);
|
47
45
|
this._cache = {};
|
48
|
-
|
49
|
-
|
50
|
-
setChangeEventsEnabled(enabled: boolean) {
|
51
|
-
this.changeEventsEnabled = enabled;
|
46
|
+
this.watcher = new RepositoryWatcher();
|
47
|
+
this.listenForChanges();
|
52
48
|
}
|
53
49
|
|
54
50
|
listenForChanges() {
|
55
|
-
|
56
|
-
|
57
|
-
FSExtra.mkdirpSync(baseDir);
|
58
|
-
}
|
59
|
-
|
60
|
-
let allDefinitions = ClusterConfiguration.getDefinitions();
|
61
|
-
|
62
|
-
console.log('Watching local repository for provider changes: %s', baseDir);
|
63
|
-
try {
|
64
|
-
this.watcher = watch(baseDir, (filename: string) => {
|
65
|
-
if (!filename) {
|
66
|
-
return;
|
67
|
-
}
|
68
|
-
|
69
|
-
const [handle, name, version] = filename.toString().split(/\//g);
|
70
|
-
if (!name || !version) {
|
71
|
-
return;
|
72
|
-
}
|
73
|
-
|
74
|
-
if (!this.changeEventsEnabled) {
|
75
|
-
return;
|
76
|
-
}
|
77
|
-
|
78
|
-
const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
|
79
|
-
const newDefinitions = ClusterConfiguration.getDefinitions();
|
80
|
-
|
81
|
-
const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
|
82
|
-
let currentDefinition = allDefinitions.find((d) => d.ymlPath === ymlPath);
|
83
|
-
const ymlExists = FS.existsSync(ymlPath);
|
84
|
-
let type;
|
85
|
-
if (ymlExists) {
|
86
|
-
if (currentDefinition) {
|
87
|
-
type = 'updated';
|
88
|
-
} else if (newDefinition) {
|
89
|
-
type = 'added';
|
90
|
-
currentDefinition = newDefinition;
|
91
|
-
} else {
|
92
|
-
//Other definition was added / updated - ignore
|
93
|
-
return;
|
94
|
-
}
|
95
|
-
} else {
|
96
|
-
if (currentDefinition) {
|
97
|
-
const ref = parseKapetaUri(
|
98
|
-
`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`
|
99
|
-
).id;
|
100
|
-
delete INSTALL_ATTEMPTED[ref];
|
101
|
-
//Something was removed
|
102
|
-
type = 'removed';
|
103
|
-
} else {
|
104
|
-
//Other definition was removed - ignore
|
105
|
-
return;
|
106
|
-
}
|
107
|
-
}
|
51
|
+
this.watcher.watch();
|
52
|
+
}
|
108
53
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
asset: { handle, name, version },
|
113
|
-
};
|
54
|
+
async stopListening() {
|
55
|
+
return this.watcher.unwatch();
|
56
|
+
}
|
114
57
|
|
115
|
-
|
116
|
-
|
117
|
-
definitionsManager.clearCache();
|
118
|
-
});
|
119
|
-
} catch (e) {
|
120
|
-
// Fallback to run without watch mode due to potential platform issues.
|
121
|
-
// https://nodejs.org/docs/latest/api/fs.html#caveats
|
122
|
-
console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
|
123
|
-
return;
|
124
|
-
}
|
58
|
+
ignoreChangesFor(file: string) {
|
59
|
+
return this.watcher.ignoreChangesFor(file);
|
125
60
|
}
|
126
61
|
|
127
|
-
|
128
|
-
|
129
|
-
return;
|
130
|
-
}
|
131
|
-
this.watcher();
|
132
|
-
this.watcher = undefined;
|
62
|
+
resumeChangedFor(file: string) {
|
63
|
+
return this.watcher.resumeChangedFor(file);
|
133
64
|
}
|
134
65
|
|
135
66
|
public ensureDefaultProviders(): void {
|
136
|
-
socketManager.emitGlobal(EVENT_DEFAULT_PROVIDERS_START, {providers:DEFAULT_PROVIDERS});
|
67
|
+
socketManager.emitGlobal(EVENT_DEFAULT_PROVIDERS_START, { providers: DEFAULT_PROVIDERS });
|
137
68
|
const tasks = this._install(DEFAULT_PROVIDERS);
|
138
|
-
Promise.allSettled(tasks.map(t => t.wait())).then(() => {
|
69
|
+
Promise.allSettled(tasks.map((t) => t.wait())).then(() => {
|
139
70
|
socketManager.emitGlobal(EVENT_DEFAULT_PROVIDERS_END, {});
|
140
71
|
});
|
141
72
|
}
|
@@ -157,17 +88,12 @@ class RepositoryManager {
|
|
157
88
|
try {
|
158
89
|
//We change to a temp dir to avoid issues with the current working directory
|
159
90
|
process.chdir(os.tmpdir());
|
160
|
-
|
161
|
-
this.setChangeEventsEnabled(false);
|
162
|
-
await Actions.install(progressListener, [ref], {});
|
91
|
+
await Actions.install(new ProgressListener(), [ref], {});
|
163
92
|
} catch (e) {
|
164
93
|
console.error(`Failed to install asset: ${ref}`, e);
|
165
94
|
throw e;
|
166
|
-
} finally {
|
167
|
-
this.setChangeEventsEnabled(true);
|
168
95
|
}
|
169
|
-
|
170
|
-
assetManager.clearCache();
|
96
|
+
clearAllCaches();
|
171
97
|
//console.log(`Asset installed: ${ref}`);
|
172
98
|
};
|
173
99
|
};
|
package/src/socketManager.ts
CHANGED
@@ -1,9 +1,18 @@
|
|
1
1
|
import _ from 'lodash';
|
2
2
|
import { Socket, Server } from 'socket.io';
|
3
|
+
import { normalizeKapetaUri } from './utils/utils';
|
4
|
+
import { LogEntry } from './types';
|
5
|
+
export const EVENT_STATUS_CHANGED = 'status-changed';
|
6
|
+
export const EVENT_INSTANCE_CREATED = 'instance-created';
|
7
|
+
export const EVENT_INSTANCE_EXITED = 'instance-exited';
|
8
|
+
export const EVENT_INSTANCE_LOG = 'instance-log';
|
9
|
+
|
10
|
+
export const EVENT_SYSTEM_LOG = 'system-log';
|
11
|
+
export const EVENT_LOG = 'log';
|
3
12
|
|
4
13
|
export class SocketManager {
|
5
14
|
private _io: Server | null;
|
6
|
-
private _sockets: Socket[];
|
15
|
+
private readonly _sockets: Socket[];
|
7
16
|
|
8
17
|
constructor() {
|
9
18
|
this._io = null;
|
@@ -35,16 +44,46 @@ export class SocketManager {
|
|
35
44
|
this.io.emit(type, payload);
|
36
45
|
}
|
37
46
|
|
38
|
-
|
47
|
+
emitSystemEvent(systemId: string, type: string, payload: any) {
|
48
|
+
systemId = normalizeKapetaUri(systemId);
|
49
|
+
try {
|
50
|
+
socketManager.emit(`${systemId}/instances`, type, payload);
|
51
|
+
} catch (e: any) {
|
52
|
+
console.warn('Failed to emit instance event: %s', e.message);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
emitInstanceLog(systemId: string, instanceId: string, payload: LogEntry) {
|
57
|
+
this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, payload);
|
58
|
+
}
|
59
|
+
|
60
|
+
emitSystemLog(systemId: string, payload: LogEntry) {
|
61
|
+
this.emitSystemEvent(systemId, EVENT_SYSTEM_LOG, payload);
|
62
|
+
}
|
63
|
+
|
64
|
+
emitGlobalLog(payload: LogEntry) {
|
65
|
+
this.emitGlobal(EVENT_LOG, payload);
|
66
|
+
}
|
67
|
+
|
68
|
+
emitInstanceEvent(systemId: string, instanceId: string, type: string, payload: any) {
|
69
|
+
systemId = normalizeKapetaUri(systemId);
|
70
|
+
try {
|
71
|
+
socketManager.emit(`${systemId}/instances/${instanceId}`, type, payload);
|
72
|
+
} catch (e: any) {
|
73
|
+
console.warn('Failed to emit instance event: %s', e.message);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
private _bindIO() {
|
39
78
|
this.io.on('connection', (socket) => this._handleSocketCreated(socket));
|
40
79
|
}
|
41
80
|
|
42
|
-
_handleSocketCreated(socket: Socket) {
|
81
|
+
private _handleSocketCreated(socket: Socket) {
|
43
82
|
this._bindSocket(socket);
|
44
83
|
this._sockets.push(socket);
|
45
84
|
}
|
46
85
|
|
47
|
-
_bindSocket(socket: Socket) {
|
86
|
+
private _bindSocket(socket: Socket) {
|
48
87
|
socket.on('disconnect', () => this._handleSocketDestroyed(socket));
|
49
88
|
socket.on('join', (id) => {
|
50
89
|
socket.join(id);
|
@@ -54,7 +93,7 @@ export class SocketManager {
|
|
54
93
|
});
|
55
94
|
}
|
56
95
|
|
57
|
-
_handleSocketDestroyed(socket: Socket) {
|
96
|
+
private _handleSocketDestroyed(socket: Socket) {
|
58
97
|
_.pull(this._sockets, socket);
|
59
98
|
}
|
60
99
|
}
|