@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
@@ -0,0 +1,304 @@
|
|
1
|
+
import chokidar, { FSWatcher } from 'chokidar';
|
2
|
+
import ClusterConfiguration, { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
|
3
|
+
import FS from 'fs-extra';
|
4
|
+
import Path from 'node:path';
|
5
|
+
import YAML from 'yaml';
|
6
|
+
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
7
|
+
import _ from 'lodash';
|
8
|
+
import { socketManager } from './socketManager';
|
9
|
+
import { definitionsManager } from './definitionsManager';
|
10
|
+
import { assetManager } from './assetManager';
|
11
|
+
|
12
|
+
function clearAllCaches() {
|
13
|
+
definitionsManager.clearCache();
|
14
|
+
assetManager.clearCache();
|
15
|
+
}
|
16
|
+
|
17
|
+
type WatchEventName = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
|
18
|
+
interface AssetIdentity {
|
19
|
+
handle: string;
|
20
|
+
name: string;
|
21
|
+
version: string;
|
22
|
+
}
|
23
|
+
const KAPETA_YML_RX = /^kapeta.ya?ml$/;
|
24
|
+
export class RepositoryWatcher {
|
25
|
+
private watcher?: FSWatcher;
|
26
|
+
private disabled: boolean = false;
|
27
|
+
private readonly baseDir: string;
|
28
|
+
private allDefinitions: DefinitionInfo[] = [];
|
29
|
+
private symbolicLinks: { [link: string]: string } = {};
|
30
|
+
private ignoredFiles: Set<string> = new Set<string>();
|
31
|
+
constructor() {
|
32
|
+
this.baseDir = ClusterConfiguration.getRepositoryBasedir();
|
33
|
+
}
|
34
|
+
|
35
|
+
setDisabled(disabled: boolean) {
|
36
|
+
this.disabled = disabled;
|
37
|
+
}
|
38
|
+
public watch() {
|
39
|
+
if (!FS.existsSync(this.baseDir)) {
|
40
|
+
FS.mkdirpSync(this.baseDir);
|
41
|
+
}
|
42
|
+
|
43
|
+
this.allDefinitions = ClusterConfiguration.getDefinitions();
|
44
|
+
|
45
|
+
try {
|
46
|
+
this.watcher = chokidar.watch(this.baseDir, {
|
47
|
+
followSymlinks: false,
|
48
|
+
ignorePermissionErrors: true,
|
49
|
+
disableGlobbing: true,
|
50
|
+
persistent: true,
|
51
|
+
depth: 2,
|
52
|
+
ignored: (path) => this.ignoreFile(path),
|
53
|
+
});
|
54
|
+
this.watcher.on('all', this.handleFileChange.bind(this));
|
55
|
+
this.watcher.on('error', (error) => {
|
56
|
+
console.log('Error watching repository', error);
|
57
|
+
});
|
58
|
+
this.watcher.on('ready', () => {
|
59
|
+
console.log('Watching local repository for provider changes: %s', this.baseDir);
|
60
|
+
});
|
61
|
+
} catch (e) {
|
62
|
+
// Fallback to run without watch mode due to potential platform issues.
|
63
|
+
// https://nodejs.org/docs/latest/api/fs.html#caveats
|
64
|
+
console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
|
65
|
+
return;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
async ignoreChangesFor(file: string) {
|
70
|
+
this.ignoredFiles.add(file);
|
71
|
+
const realPath = await FS.realpath(file);
|
72
|
+
if (realPath !== file) {
|
73
|
+
this.ignoredFiles.add(realPath);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
async resumeChangedFor(file: string) {
|
78
|
+
this.ignoredFiles.delete(file);
|
79
|
+
const realPath = await FS.realpath(file);
|
80
|
+
if (realPath !== file) {
|
81
|
+
this.ignoredFiles.delete(realPath);
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
public async unwatch() {
|
86
|
+
if (!this.watcher) {
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
this.symbolicLinks = {};
|
90
|
+
await this.watcher.close();
|
91
|
+
this.watcher = undefined;
|
92
|
+
}
|
93
|
+
|
94
|
+
private async getAssetIdentity(path: string): Promise<AssetIdentity | undefined> {
|
95
|
+
const baseName = Path.basename(path);
|
96
|
+
let handle, name, version;
|
97
|
+
if (path.startsWith(this.baseDir)) {
|
98
|
+
const relativePath = Path.relative(this.baseDir, path);
|
99
|
+
// Inside the repo we can use the path to determine the handle, name and version
|
100
|
+
[handle, name, version] = relativePath.split(/\//g);
|
101
|
+
if (!handle || !name || !version) {
|
102
|
+
// Do nothing with this
|
103
|
+
return;
|
104
|
+
}
|
105
|
+
|
106
|
+
return {
|
107
|
+
handle,
|
108
|
+
name,
|
109
|
+
version,
|
110
|
+
};
|
111
|
+
}
|
112
|
+
|
113
|
+
if (!KAPETA_YML_RX.test(baseName)) {
|
114
|
+
// Do nothing with this
|
115
|
+
return;
|
116
|
+
}
|
117
|
+
// Outside the repo we need to use the file content to determine the handle, name
|
118
|
+
// Version is always 'local'
|
119
|
+
version = 'local';
|
120
|
+
|
121
|
+
try {
|
122
|
+
const definition: Definition = YAML.parse((await FS.readFile(path)).toString());
|
123
|
+
const uri = parseKapetaUri(definition.metadata.name);
|
124
|
+
handle = uri.handle;
|
125
|
+
name = uri.name;
|
126
|
+
return {
|
127
|
+
handle,
|
128
|
+
name,
|
129
|
+
version,
|
130
|
+
};
|
131
|
+
} catch (e) {
|
132
|
+
// Ignore issues in the YML file
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
private async handleFileChange(eventName: WatchEventName, path: string) {
|
138
|
+
if (!path) {
|
139
|
+
return;
|
140
|
+
}
|
141
|
+
|
142
|
+
if (this.ignoredFiles.has(path)) {
|
143
|
+
return;
|
144
|
+
}
|
145
|
+
|
146
|
+
//console.log('File changed', eventName, path);
|
147
|
+
|
148
|
+
const assetIdentity = await this.getAssetIdentity(path);
|
149
|
+
if (!assetIdentity) {
|
150
|
+
return;
|
151
|
+
}
|
152
|
+
|
153
|
+
if (this.disabled) {
|
154
|
+
return;
|
155
|
+
}
|
156
|
+
|
157
|
+
// If this is false it's because we're watching a symlink target
|
158
|
+
const withinRepo = path.startsWith(this.baseDir);
|
159
|
+
if (withinRepo && assetIdentity.version === 'local' && path.endsWith('/local')) {
|
160
|
+
// This is likely a symlink target
|
161
|
+
if (eventName === 'add') {
|
162
|
+
//console.log('Checking if we should add symlink target', handle, name, version, path);
|
163
|
+
await this.addSymlinkTarget(path);
|
164
|
+
}
|
165
|
+
|
166
|
+
if (eventName === 'unlink') {
|
167
|
+
await this.removeSymlinkTarget(path);
|
168
|
+
}
|
169
|
+
|
170
|
+
if (eventName === 'change') {
|
171
|
+
await this.updateSymlinkTarget(path);
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
await this.checkForChange(assetIdentity);
|
176
|
+
}
|
177
|
+
|
178
|
+
private async checkForChange(assetIdentity: AssetIdentity) {
|
179
|
+
const ymlPath = Path.join(
|
180
|
+
this.baseDir,
|
181
|
+
assetIdentity.handle,
|
182
|
+
assetIdentity.name,
|
183
|
+
assetIdentity.version,
|
184
|
+
'kapeta.yml'
|
185
|
+
);
|
186
|
+
const newDefinitions = ClusterConfiguration.getDefinitions();
|
187
|
+
const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
|
188
|
+
let currentDefinition = this.allDefinitions.find((d) => d.ymlPath === ymlPath);
|
189
|
+
const ymlExists = await this.exists(ymlPath);
|
190
|
+
let type;
|
191
|
+
if (ymlExists) {
|
192
|
+
if (currentDefinition) {
|
193
|
+
if (newDefinition && _.isEqual(currentDefinition, newDefinition)) {
|
194
|
+
//Definition was not changed
|
195
|
+
return;
|
196
|
+
}
|
197
|
+
type = 'updated';
|
198
|
+
} else if (newDefinition) {
|
199
|
+
type = 'added';
|
200
|
+
currentDefinition = newDefinition;
|
201
|
+
} else {
|
202
|
+
//Other definition was added / updated - ignore
|
203
|
+
return;
|
204
|
+
}
|
205
|
+
} else {
|
206
|
+
if (currentDefinition) {
|
207
|
+
const ref = parseKapetaUri(
|
208
|
+
`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`
|
209
|
+
).id;
|
210
|
+
//Something was removed
|
211
|
+
type = 'removed';
|
212
|
+
} else {
|
213
|
+
//Other definition was removed - ignore
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
const payload = {
|
219
|
+
type,
|
220
|
+
definition: newDefinition?.definition ?? currentDefinition?.definition,
|
221
|
+
asset: assetIdentity,
|
222
|
+
};
|
223
|
+
|
224
|
+
this.allDefinitions = newDefinitions;
|
225
|
+
|
226
|
+
//console.log('Asset changed', payload);
|
227
|
+
socketManager.emitGlobal('asset-change', payload);
|
228
|
+
clearAllCaches();
|
229
|
+
}
|
230
|
+
|
231
|
+
private async exists(path: string): Promise<boolean> {
|
232
|
+
try {
|
233
|
+
await FS.access(path);
|
234
|
+
return true;
|
235
|
+
} catch (e) {
|
236
|
+
return false;
|
237
|
+
}
|
238
|
+
}
|
239
|
+
private async removeSymlinkTarget(path: string) {
|
240
|
+
if (this.symbolicLinks[path]) {
|
241
|
+
//console.log('Unwatching symlink target %s => %s', path, this.symbolicLinks[path]);
|
242
|
+
this.watcher?.unwatch(this.symbolicLinks[path]);
|
243
|
+
delete this.symbolicLinks[path];
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
private async updateSymlinkTarget(path: string) {
|
248
|
+
if (this.symbolicLinks[path]) {
|
249
|
+
//console.log('Updating symlink target %s => %s', path, this.symbolicLinks[path]);
|
250
|
+
this.watcher?.unwatch(this.symbolicLinks[path]);
|
251
|
+
delete this.symbolicLinks[path];
|
252
|
+
await this.addSymlinkTarget(path);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
private async addSymlinkTarget(path: string) {
|
257
|
+
try {
|
258
|
+
// Make sure we're not watching the symlink target
|
259
|
+
await this.removeSymlinkTarget(path);
|
260
|
+
const stat = await FS.lstat(path);
|
261
|
+
if (stat.isSymbolicLink()) {
|
262
|
+
const realPath = `${await FS.realpath(path)}/kapeta.yml`;
|
263
|
+
if (await this.exists(realPath)) {
|
264
|
+
//console.log('Watching symlink target %s => %s', path, realPath);
|
265
|
+
this.watcher?.add(realPath);
|
266
|
+
this.symbolicLinks[path] = realPath;
|
267
|
+
}
|
268
|
+
}
|
269
|
+
} catch (e) {
|
270
|
+
// Ignore
|
271
|
+
console.warn('Failed to check local symlink target', e);
|
272
|
+
}
|
273
|
+
}
|
274
|
+
|
275
|
+
private ignoreFile(path: string) {
|
276
|
+
if (!path.startsWith(this.baseDir)) {
|
277
|
+
return false;
|
278
|
+
}
|
279
|
+
if (path.includes('/node_modules/')) {
|
280
|
+
return true;
|
281
|
+
}
|
282
|
+
|
283
|
+
const filename = Path.basename(path);
|
284
|
+
if (filename.startsWith('.')) {
|
285
|
+
return true;
|
286
|
+
}
|
287
|
+
|
288
|
+
const relativePath = Path.relative(this.baseDir, path).split(Path.sep);
|
289
|
+
|
290
|
+
try {
|
291
|
+
if (FS.statSync(path).isDirectory()) {
|
292
|
+
if (relativePath.length > 3) {
|
293
|
+
return true;
|
294
|
+
}
|
295
|
+
return false;
|
296
|
+
}
|
297
|
+
} catch (e) {
|
298
|
+
// Didn't exist - dont ignore
|
299
|
+
return false;
|
300
|
+
}
|
301
|
+
|
302
|
+
return !/^kapeta\.ya?ml$/.test(filename);
|
303
|
+
}
|
304
|
+
}
|
package/src/assetManager.ts
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
import Path from 'node:path';
|
2
|
-
import FS from '
|
3
|
-
import FSExtra from 'fs-extra';
|
2
|
+
import FS from 'fs-extra';
|
4
3
|
import YAML from 'yaml';
|
5
4
|
import NodeCache from 'node-cache';
|
6
|
-
import
|
5
|
+
import { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
|
7
6
|
import { codeGeneratorManager } from './codeGeneratorManager';
|
8
|
-
import {
|
7
|
+
import { ProgressListener } from './progressListener';
|
9
8
|
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
10
9
|
import { repositoryManager } from './repositoryManager';
|
11
10
|
import { BlockDefinition } from '@kapeta/schemas';
|
@@ -14,6 +13,11 @@ import { definitionsManager } from './definitionsManager';
|
|
14
13
|
import { normalizeKapetaUri } from './utils/utils';
|
15
14
|
import { taskManager } from './taskManager';
|
16
15
|
|
16
|
+
function clearAllCaches() {
|
17
|
+
definitionsManager.clearCache();
|
18
|
+
assetManager.clearCache();
|
19
|
+
}
|
20
|
+
|
17
21
|
export interface EnrichedAsset {
|
18
22
|
ref: string;
|
19
23
|
editable: boolean;
|
@@ -133,23 +137,19 @@ class AssetManager {
|
|
133
137
|
}
|
134
138
|
|
135
139
|
async createAsset(path: string, yaml: BlockDefinition): Promise<EnrichedAsset[]> {
|
136
|
-
if (FS.
|
140
|
+
if (await FS.pathExists(path)) {
|
137
141
|
throw new Error('File already exists: ' + path);
|
138
142
|
}
|
139
143
|
|
140
144
|
const dirName = Path.dirname(path);
|
141
|
-
if (!FS.
|
142
|
-
|
145
|
+
if (!(await FS.pathExists(dirName))) {
|
146
|
+
await FS.mkdirp(dirName);
|
143
147
|
}
|
144
148
|
|
145
|
-
|
146
|
-
FS.writeFileSync(path, YAML.stringify(yaml));
|
147
|
-
|
149
|
+
await FS.writeFile(path, YAML.stringify(yaml));
|
148
150
|
const asset = await this.importFile(path);
|
149
|
-
console.log('Imported');
|
150
151
|
|
151
|
-
|
152
|
-
definitionsManager.clearCache();
|
152
|
+
clearAllCaches();
|
153
153
|
|
154
154
|
const ref = `kapeta://${yaml.metadata.name}:local`;
|
155
155
|
|
@@ -172,12 +172,21 @@ class AssetManager {
|
|
172
172
|
throw new Error('Attempted to update corrupted asset: ' + ref);
|
173
173
|
}
|
174
174
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
175
|
+
const path = asset.ymlPath;
|
176
|
+
try {
|
177
|
+
await repositoryManager.ignoreChangesFor(path);
|
178
|
+
await FS.writeFile(asset.ymlPath, YAML.stringify(yaml));
|
179
|
+
console.log('Wrote to ' + asset.ymlPath);
|
180
|
+
clearAllCaches();
|
181
|
+
this.maybeGenerateCode(asset.ref, asset.ymlPath, yaml);
|
182
|
+
} finally {
|
183
|
+
//We need to wait a bit for the disk to settle before we can resume watching
|
184
|
+
setTimeout(async () => {
|
185
|
+
try {
|
186
|
+
await repositoryManager.resumeChangedFor(path);
|
187
|
+
} catch (e) {}
|
188
|
+
}, 500);
|
189
|
+
}
|
181
190
|
}
|
182
191
|
|
183
192
|
private maybeGenerateCode(ref: string, ymlPath: string, block: BlockDefinition) {
|
@@ -203,17 +212,20 @@ class AssetManager {
|
|
203
212
|
filePath = filePath.substring('file://'.length);
|
204
213
|
}
|
205
214
|
|
206
|
-
if (!FS.
|
215
|
+
if (!(await FS.pathExists(filePath))) {
|
207
216
|
throw new Error('File not found: ' + filePath);
|
208
217
|
}
|
218
|
+
const content = await FS.readFile(filePath);
|
209
219
|
|
210
|
-
const assetInfos = YAML.parseAllDocuments(
|
220
|
+
const assetInfos = YAML.parseAllDocuments(content.toString()).map((doc) => doc.toJSON());
|
211
221
|
|
212
|
-
await Actions.link(
|
222
|
+
await Actions.link(new ProgressListener(), Path.dirname(filePath));
|
213
223
|
|
214
224
|
const version = 'local';
|
215
225
|
const refs = assetInfos.map((assetInfo) => `kapeta://${assetInfo.metadata.name}:${version}`);
|
216
|
-
|
226
|
+
|
227
|
+
clearAllCaches();
|
228
|
+
|
217
229
|
return this.getAssets().filter((a) => refs.some((ref) => compareRefs(ref, a.ref)));
|
218
230
|
}
|
219
231
|
|
@@ -222,8 +234,10 @@ class AssetManager {
|
|
222
234
|
if (!asset) {
|
223
235
|
throw new Error('Asset does not exists: ' + ref);
|
224
236
|
}
|
225
|
-
|
226
|
-
|
237
|
+
|
238
|
+
clearAllCaches();
|
239
|
+
|
240
|
+
await Actions.uninstall(new ProgressListener(), [asset.ref]);
|
227
241
|
}
|
228
242
|
|
229
243
|
async installAsset(ref: string) {
|
package/src/containerManager.ts
CHANGED
@@ -11,14 +11,9 @@ import uuid from 'node-uuid';
|
|
11
11
|
import md5 from 'md5';
|
12
12
|
import { getBlockInstanceContainerName } from './utils/utils';
|
13
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
14
|
import { KapetaAPI } from '@kapeta/nodejs-api-client';
|
18
15
|
import { taskManager, Task } from './taskManager';
|
19
16
|
|
20
|
-
const EVENT_IMAGE_PULL = 'docker-image-pull';
|
21
|
-
|
22
17
|
type StringMap = { [key: string]: string };
|
23
18
|
|
24
19
|
export type PortMap = {
|
package/src/identities/routes.ts
CHANGED
@@ -5,12 +5,12 @@ import { corsHandler } from '../middleware/cors';
|
|
5
5
|
import { Request, Response } from 'express';
|
6
6
|
|
7
7
|
const router = Router();
|
8
|
-
const api = new KapetaAPI();
|
9
8
|
|
10
9
|
router.use('/', corsHandler);
|
11
10
|
|
12
11
|
router.get('/current', async (req: Request, res: Response) => {
|
13
12
|
try {
|
13
|
+
const api = new KapetaAPI();
|
14
14
|
if (api.hasToken()) {
|
15
15
|
res.send(await api.getCurrentIdentity());
|
16
16
|
} else {
|
@@ -23,6 +23,7 @@ router.get('/current', async (req: Request, res: Response) => {
|
|
23
23
|
|
24
24
|
router.get('/:identityId/memberships', async (req: Request, res: Response) => {
|
25
25
|
try {
|
26
|
+
const api = new KapetaAPI();
|
26
27
|
if (api.hasToken()) {
|
27
28
|
res.send(await api.getMemberships(req.params.identityId));
|
28
29
|
} else {
|
package/src/instanceManager.ts
CHANGED
@@ -3,7 +3,13 @@ import request from 'request';
|
|
3
3
|
import AsyncLock from 'async-lock';
|
4
4
|
import { BlockInstanceRunner } from './utils/BlockInstanceRunner';
|
5
5
|
import { storageService } from './storageService';
|
6
|
-
import {
|
6
|
+
import {
|
7
|
+
EVENT_INSTANCE_CREATED,
|
8
|
+
EVENT_INSTANCE_EXITED,
|
9
|
+
EVENT_INSTANCE_LOG,
|
10
|
+
EVENT_STATUS_CHANGED,
|
11
|
+
socketManager,
|
12
|
+
} from './socketManager';
|
7
13
|
import { serviceManager } from './serviceManager';
|
8
14
|
import { assetManager } from './assetManager';
|
9
15
|
import { containerManager, HEALTH_CHECK_TIMEOUT } from './containerManager';
|
@@ -19,10 +25,6 @@ import { Task, taskManager } from './taskManager';
|
|
19
25
|
const CHECK_INTERVAL = 5000;
|
20
26
|
const DEFAULT_HEALTH_PORT_TYPE = 'rest';
|
21
27
|
|
22
|
-
const EVENT_STATUS_CHANGED = 'status-changed';
|
23
|
-
const EVENT_INSTANCE_CREATED = 'instance-created';
|
24
|
-
const EVENT_INSTANCE_EXITED = 'instance-exited';
|
25
|
-
const EVENT_INSTANCE_LOG = 'instance-log';
|
26
28
|
const MIN_TIME_RUNNING = 30000; //If something didnt run for more than 30 secs - it failed
|
27
29
|
|
28
30
|
export class InstanceManager {
|
@@ -72,8 +74,11 @@ export class InstanceManager {
|
|
72
74
|
}
|
73
75
|
|
74
76
|
const plan = planInfo.definition as Plan;
|
77
|
+
if (!plan?.spec?.blocks) {
|
78
|
+
return [];
|
79
|
+
}
|
75
80
|
|
76
|
-
const instanceIds = plan.spec
|
81
|
+
const instanceIds = plan.spec?.blocks?.map((block) => block.id) || [];
|
77
82
|
|
78
83
|
return this._instances.filter(
|
79
84
|
(instance) => instance.systemId === systemId && instanceIds.includes(instance.instanceId)
|
@@ -153,10 +158,10 @@ export class InstanceManager {
|
|
153
158
|
if (existingInstance) {
|
154
159
|
const ix = this._instances.indexOf(existingInstance);
|
155
160
|
this._instances.splice(ix, 1, instance);
|
156
|
-
|
161
|
+
socketManager.emitSystemEvent(instance.systemId, EVENT_STATUS_CHANGED, instance);
|
157
162
|
} else {
|
158
163
|
this._instances.push(instance);
|
159
|
-
|
164
|
+
socketManager.emitSystemEvent(instance.systemId, EVENT_INSTANCE_CREATED, instance);
|
160
165
|
}
|
161
166
|
|
162
167
|
this.save();
|
@@ -222,7 +227,7 @@ export class InstanceManager {
|
|
222
227
|
instance.health = healthUrl;
|
223
228
|
}
|
224
229
|
|
225
|
-
|
230
|
+
socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
226
231
|
} else {
|
227
232
|
//If instance was not found - then we're receiving an externally started instance
|
228
233
|
instance = {
|
@@ -239,7 +244,7 @@ export class InstanceManager {
|
|
239
244
|
|
240
245
|
this._instances.push(instance);
|
241
246
|
|
242
|
-
|
247
|
+
socketManager.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
|
243
248
|
}
|
244
249
|
|
245
250
|
this.save();
|
@@ -268,7 +273,7 @@ export class InstanceManager {
|
|
268
273
|
instance.status = InstanceStatus.STOPPED;
|
269
274
|
instance.pid = null;
|
270
275
|
instance.health = null;
|
271
|
-
|
276
|
+
socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
272
277
|
this.save();
|
273
278
|
}
|
274
279
|
});
|
@@ -343,7 +348,7 @@ export class InstanceManager {
|
|
343
348
|
|
344
349
|
instance.status = InstanceStatus.STOPPING;
|
345
350
|
|
346
|
-
|
351
|
+
socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
347
352
|
console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
|
348
353
|
this.save();
|
349
354
|
|
@@ -355,7 +360,7 @@ export class InstanceManager {
|
|
355
360
|
try {
|
356
361
|
await container.stop();
|
357
362
|
instance.status = InstanceStatus.STOPPED;
|
358
|
-
|
363
|
+
socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
359
364
|
this.save();
|
360
365
|
} catch (e) {
|
361
366
|
console.error('Failed to stop container', e);
|
@@ -374,7 +379,7 @@ export class InstanceManager {
|
|
374
379
|
|
375
380
|
process.kill(instance.pid as number, 'SIGTERM');
|
376
381
|
instance.status = InstanceStatus.STOPPED;
|
377
|
-
|
382
|
+
socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
|
378
383
|
this.save();
|
379
384
|
} catch (e) {
|
380
385
|
console.error('Failed to stop process', e);
|
@@ -528,9 +533,9 @@ export class InstanceManager {
|
|
528
533
|
errorMessage: e.message ?? 'Failed to start - Check logs for details.',
|
529
534
|
});
|
530
535
|
|
531
|
-
|
536
|
+
socketManager.emitInstanceLog(systemId, instanceId, logs[0]);
|
532
537
|
|
533
|
-
|
538
|
+
socketManager.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
|
534
539
|
error: `Failed to start instance: ${e.message}`,
|
535
540
|
status: EVENT_INSTANCE_EXITED,
|
536
541
|
instanceId: blockInstance.id,
|
@@ -645,7 +650,7 @@ export class InstanceManager {
|
|
645
650
|
oldStatus,
|
646
651
|
instance.status
|
647
652
|
);
|
648
|
-
|
653
|
+
socketManager.emitSystemEvent(instance.systemId, EVENT_STATUS_CHANGED, instance);
|
649
654
|
changed = true;
|
650
655
|
}
|
651
656
|
}
|
@@ -802,24 +807,6 @@ export class InstanceManager {
|
|
802
807
|
});
|
803
808
|
});
|
804
809
|
}
|
805
|
-
|
806
|
-
private emitSystemEvent(systemId: string, type: string, payload: any) {
|
807
|
-
systemId = normalizeKapetaUri(systemId);
|
808
|
-
try {
|
809
|
-
socketManager.emit(`${systemId}/instances`, type, payload);
|
810
|
-
} catch (e: any) {
|
811
|
-
console.warn('Failed to emit instance event: %s', e.message);
|
812
|
-
}
|
813
|
-
}
|
814
|
-
|
815
|
-
private emitInstanceEvent(systemId: string, instanceId: string, type: string, payload: any) {
|
816
|
-
systemId = normalizeKapetaUri(systemId);
|
817
|
-
try {
|
818
|
-
socketManager.emit(`${systemId}/instances/${instanceId}`, type, payload);
|
819
|
-
} catch (e: any) {
|
820
|
-
console.warn('Failed to emit instance event: %s', e.message);
|
821
|
-
}
|
822
|
-
}
|
823
810
|
}
|
824
811
|
|
825
812
|
export const instanceManager = new InstanceManager();
|