@kapeta/local-cluster-service 0.0.66 → 0.0.68
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/package.json +3 -1
- package/src/assetManager.js +22 -98
- package/src/assets/routes.js +2 -2
- package/src/codeGeneratorManager.js +5 -0
- package/src/instanceManager.js +3 -3
- package/src/progressListener.js +82 -0
- package/src/providerManager.js +23 -46
- package/src/providers/routes.js +30 -8
- package/src/repositoryManager.js +201 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.68",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"main": "index.js",
|
6
6
|
"repository": {
|
@@ -24,6 +24,7 @@
|
|
24
24
|
"@kapeta/local-cluster-config": "<2",
|
25
25
|
"@kapeta/local-cluster-executor": "<2",
|
26
26
|
"@kapeta/nodejs-api-client": "<2",
|
27
|
+
"@kapeta/nodejs-registry-utils": "^0.0.2",
|
27
28
|
"@kapeta/nodejs-utils": "<2",
|
28
29
|
"@kapeta/sdk-config": "<2",
|
29
30
|
"express": "4.17.1",
|
@@ -36,6 +37,7 @@
|
|
36
37
|
"node-docker-api": "1.1.22",
|
37
38
|
"node-uuid": "^1.4.8",
|
38
39
|
"request": "2.88.2",
|
40
|
+
"request-promise": "4.2.6",
|
39
41
|
"socket.io": "^4.5.2",
|
40
42
|
"yaml": "^1.6.0"
|
41
43
|
},
|
package/src/assetManager.js
CHANGED
@@ -3,13 +3,11 @@ const FS = require('node:fs');
|
|
3
3
|
const FSExtra = require('fs-extra');
|
4
4
|
const YAML = require('yaml');
|
5
5
|
const ClusterConfiguration = require('@kapeta/local-cluster-config');
|
6
|
+
const {Actions} = require('@kapeta/nodejs-registry-utils');
|
6
7
|
const codeGeneratorManager = require('./codeGeneratorManager');
|
7
|
-
const
|
8
|
-
|
9
|
-
|
10
|
-
FSExtra.mkdirpSync(Path.dirname(versionTarget));
|
11
|
-
FSExtra.createSymlinkSync(directory, versionTarget);
|
12
|
-
}
|
8
|
+
const progressListener = require('./progressListener');
|
9
|
+
const {parseKapetaUri} = require("@kapeta/nodejs-utils");
|
10
|
+
const repositoryManager = require("./repositoryManager");
|
13
11
|
|
14
12
|
function enrichAsset(asset) {
|
15
13
|
return {
|
@@ -47,77 +45,6 @@ function parseRef(ref) {
|
|
47
45
|
|
48
46
|
class AssetManager {
|
49
47
|
|
50
|
-
constructor() {
|
51
|
-
this.watcher = null;
|
52
|
-
this.listenForChanges();
|
53
|
-
}
|
54
|
-
|
55
|
-
listenForChanges() {
|
56
|
-
const baseDir = ClusterConfiguration.getRepositoryBasedir();
|
57
|
-
if (!FS.existsSync(baseDir)) {
|
58
|
-
FSExtra.mkdirpSync(baseDir);
|
59
|
-
}
|
60
|
-
|
61
|
-
let currentWebDefinitions = ClusterConfiguration
|
62
|
-
.getProviderDefinitions()
|
63
|
-
.filter(d => d.hasWeb);
|
64
|
-
|
65
|
-
console.log('Watching local repository for provider changes: %s', baseDir);
|
66
|
-
try {
|
67
|
-
this.watcher = FS.watch(baseDir, { recursive: true });
|
68
|
-
} catch (e) {
|
69
|
-
// Fallback to run without watch mode due to potential platform issues.
|
70
|
-
// https://nodejs.org/docs/latest/api/fs.html#caveats
|
71
|
-
console.log('Unable to watch for changes. Changes to assets will not update automatically.');
|
72
|
-
return;
|
73
|
-
}
|
74
|
-
this.watcher.on('change', (eventType, filename) => {
|
75
|
-
const [handle, name, version] = filename.split(/\//g);
|
76
|
-
if (!name || !version) {
|
77
|
-
return;
|
78
|
-
}
|
79
|
-
|
80
|
-
const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
|
81
|
-
const newWebDefinitions = ClusterConfiguration
|
82
|
-
.getProviderDefinitions()
|
83
|
-
.filter(d => d.hasWeb);
|
84
|
-
|
85
|
-
const newWebDefinition = newWebDefinitions.find(d => d.ymlPath === ymlPath);
|
86
|
-
let currentWebDefinition = currentWebDefinitions.find(d => d.ymlPath === ymlPath);
|
87
|
-
const ymlExists = FS.existsSync(ymlPath);
|
88
|
-
let type;
|
89
|
-
if (ymlExists) {
|
90
|
-
if (currentWebDefinition) {
|
91
|
-
type = 'updated';
|
92
|
-
} else if (newWebDefinition) {
|
93
|
-
type = 'added';
|
94
|
-
currentWebDefinition = newWebDefinition;
|
95
|
-
} else {
|
96
|
-
//Other definition was added / updated - ignore
|
97
|
-
return;
|
98
|
-
}
|
99
|
-
} else {
|
100
|
-
if (currentWebDefinition) {
|
101
|
-
//Something was removed
|
102
|
-
type = 'removed';
|
103
|
-
} else {
|
104
|
-
//Other definition was removed - ignore
|
105
|
-
return;
|
106
|
-
}
|
107
|
-
}
|
108
|
-
|
109
|
-
const payload = {type, definition: currentWebDefinition?.definition, asset: {handle, name, version} };
|
110
|
-
|
111
|
-
currentWebDefinitions = newWebDefinitions
|
112
|
-
|
113
|
-
socketManager.emit(`assets`, 'changed', payload);
|
114
|
-
});
|
115
|
-
}
|
116
|
-
|
117
|
-
stopListening() {
|
118
|
-
this.watcher.close();
|
119
|
-
this.watcher = null;
|
120
|
-
}
|
121
48
|
|
122
49
|
|
123
50
|
/**
|
@@ -143,8 +70,8 @@ class AssetManager {
|
|
143
70
|
return this.getAssets(['core/plan']);
|
144
71
|
}
|
145
72
|
|
146
|
-
getPlan(ref) {
|
147
|
-
const asset = this.getAsset(ref);
|
73
|
+
async getPlan(ref) {
|
74
|
+
const asset = await this.getAsset(ref);
|
148
75
|
|
149
76
|
if ('core/plan' !== asset.kind) {
|
150
77
|
throw new Error('Asset was not a plan: ' + ref);
|
@@ -153,8 +80,15 @@ class AssetManager {
|
|
153
80
|
return asset.data;
|
154
81
|
}
|
155
82
|
|
156
|
-
getAsset(ref) {
|
157
|
-
const
|
83
|
+
async getAsset(ref) {
|
84
|
+
const uri = parseKapetaUri(ref);
|
85
|
+
|
86
|
+
await repositoryManager.ensureAsset(uri.handle, uri.name, uri.version);
|
87
|
+
|
88
|
+
let asset = ClusterConfiguration.getDefinitions()
|
89
|
+
.map(enrichAsset)
|
90
|
+
.find(a => parseKapetaUri(a.ref).equals(uri));
|
91
|
+
|
158
92
|
if (!asset) {
|
159
93
|
throw new Error('Asset not found: ' + ref);
|
160
94
|
}
|
@@ -184,7 +118,7 @@ class AssetManager {
|
|
184
118
|
}
|
185
119
|
|
186
120
|
async updateAsset(ref, yaml) {
|
187
|
-
const asset = this.getAsset(ref);
|
121
|
+
const asset = await this.getAsset(ref);
|
188
122
|
if (!asset) {
|
189
123
|
throw new Error('Attempted to update unknown asset: ' + ref);
|
190
124
|
}
|
@@ -206,10 +140,6 @@ class AssetManager {
|
|
206
140
|
}
|
207
141
|
}
|
208
142
|
|
209
|
-
hasAsset(ref) {
|
210
|
-
return !!this.getAsset(ref);
|
211
|
-
}
|
212
|
-
|
213
143
|
async importFile(filePath) {
|
214
144
|
if (filePath.startsWith('file://')) {
|
215
145
|
filePath = filePath.substring('file://'.length);
|
@@ -222,27 +152,21 @@ class AssetManager {
|
|
222
152
|
const assetInfos = YAML.parseAllDocuments(FS.readFileSync(filePath).toString())
|
223
153
|
.map(doc => doc.toJSON());
|
224
154
|
|
225
|
-
|
226
|
-
const version = 'local';
|
227
|
-
const [handle, name] = assetInfo.metadata.name.split('/');
|
228
|
-
|
229
|
-
const target = ClusterConfiguration.getRepositoryAssetPath(handle, name, version);
|
230
|
-
if (!FS.existsSync(target)) {
|
231
|
-
makeSymLink(Path.dirname(filePath), target);
|
232
|
-
}
|
155
|
+
await Actions.link(progressListener, Path.dirname(filePath));
|
233
156
|
|
157
|
+
const version = 'local';
|
234
158
|
const refs = assetInfos.map(assetInfo => `kapeta://${assetInfo.metadata.name}:${version}`);
|
235
159
|
|
236
160
|
return this.getAssets().filter(a => refs.some(ref => compareRefs(ref, a.ref)));
|
237
161
|
}
|
238
162
|
|
239
|
-
unregisterAsset(ref) {
|
240
|
-
const asset = this.getAsset(ref);
|
163
|
+
async unregisterAsset(ref) {
|
164
|
+
const asset = await this.getAsset(ref);
|
241
165
|
if (!asset) {
|
242
166
|
throw new Error('Asset does not exists: ' + ref);
|
243
167
|
}
|
244
|
-
|
245
|
-
|
168
|
+
|
169
|
+
await Actions.uninstall(progressListener, asset.path);
|
246
170
|
}
|
247
171
|
}
|
248
172
|
|
package/src/assets/routes.js
CHANGED
@@ -34,14 +34,14 @@ router.get('/', (req, res) => {
|
|
34
34
|
/**
|
35
35
|
* Get single asset
|
36
36
|
*/
|
37
|
-
router.get('/read', (req, res) => {
|
37
|
+
router.get('/read', async (req, res) => {
|
38
38
|
if (!req.query.ref) {
|
39
39
|
res.status(400).send({error:'Query parameter "ref" is missing'});
|
40
40
|
return;
|
41
41
|
}
|
42
42
|
|
43
43
|
try {
|
44
|
-
res.send(assetManager.getAsset(req.query.ref));
|
44
|
+
res.send(await assetManager.getAsset(req.query.ref));
|
45
45
|
} catch(err) {
|
46
46
|
res.status(400).send({error: err.message});
|
47
47
|
}
|
@@ -22,6 +22,11 @@ class CodeGeneratorManager {
|
|
22
22
|
}
|
23
23
|
|
24
24
|
canGenerateCode(yamlContent) {
|
25
|
+
if (!yamlContent.spec.target?.kind) {
|
26
|
+
//Not all block types have targets
|
27
|
+
return false;
|
28
|
+
}
|
29
|
+
|
25
30
|
const blockTypes = ClusterConfiguration.getDefinitions(BLOCK_TYPE_KIND);
|
26
31
|
const blockTypeKinds = blockTypes.map(blockType => blockType.definition.metadata.name.toLowerCase() + ':' + blockType.version);
|
27
32
|
return yamlContent && yamlContent.kind && blockTypeKinds.indexOf(yamlContent.kind.toLowerCase()) > -1;
|
package/src/instanceManager.js
CHANGED
@@ -228,7 +228,7 @@ class InstanceManager {
|
|
228
228
|
async createProcessesForPlan(planRef) {
|
229
229
|
await this.stopAllForPlan(planRef);
|
230
230
|
|
231
|
-
const plan = assetManager.getPlan(planRef);
|
231
|
+
const plan = await assetManager.getPlan(planRef);
|
232
232
|
if (!plan) {
|
233
233
|
throw new Error('Plan not found: ' + planRef);
|
234
234
|
}
|
@@ -301,7 +301,7 @@ class InstanceManager {
|
|
301
301
|
* @return {Promise<PromiseInfo>}
|
302
302
|
*/
|
303
303
|
async createProcess(planRef, instanceId) {
|
304
|
-
const plan = assetManager.getPlan(planRef);
|
304
|
+
const plan = await assetManager.getPlan(planRef);
|
305
305
|
if (!plan) {
|
306
306
|
throw new Error('Plan not found: ' + planRef);
|
307
307
|
}
|
@@ -313,7 +313,7 @@ class InstanceManager {
|
|
313
313
|
|
314
314
|
const blockRef = blockInstance.block.ref;
|
315
315
|
|
316
|
-
const blockAsset = assetManager.getAsset(blockRef);
|
316
|
+
const blockAsset = await assetManager.getAsset(blockRef);
|
317
317
|
|
318
318
|
if (!blockAsset) {
|
319
319
|
throw new Error('Block not found: ' + blockRef);
|
@@ -0,0 +1,82 @@
|
|
1
|
+
const {spawn} = require("child_process");
|
2
|
+
const socketManager = require("./socketManager");
|
3
|
+
/**
|
4
|
+
*
|
5
|
+
* @type {ProgressListener}
|
6
|
+
*/
|
7
|
+
module.exports = {
|
8
|
+
run: (command, directory) => {
|
9
|
+
socketManager.emit(
|
10
|
+
`install`,
|
11
|
+
'install:log',
|
12
|
+
{
|
13
|
+
type: 'info',
|
14
|
+
message: `Running command "${command}"`
|
15
|
+
}
|
16
|
+
);
|
17
|
+
|
18
|
+
return new Promise((resolve, reject) => {
|
19
|
+
const child = spawn(command, {
|
20
|
+
cwd: directory ? directory : process.cwd(),
|
21
|
+
detached: true,
|
22
|
+
shell: true
|
23
|
+
});
|
24
|
+
|
25
|
+
child.stdout.on('data', (data) => {
|
26
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: data.toString()});
|
27
|
+
});
|
28
|
+
|
29
|
+
child.stderr.on('data', (data) => {
|
30
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: data.toString()});
|
31
|
+
});
|
32
|
+
|
33
|
+
child.on('exit', (exit, signal) => {
|
34
|
+
if (exit !== 0) {
|
35
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: `"${command}" failed: "${exit}"`});
|
36
|
+
reject(new Error(`Command "${command}" exited with code ${exit}`));
|
37
|
+
} else {
|
38
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: `Command OK: "${command}"`});
|
39
|
+
resolve({exit, signal});
|
40
|
+
}
|
41
|
+
});
|
42
|
+
|
43
|
+
child.on('error', (err) => {
|
44
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: `"${command}" failed: "${err.message}"`});
|
45
|
+
reject(err);
|
46
|
+
});
|
47
|
+
});
|
48
|
+
},
|
49
|
+
progress: async (label, callback) => {
|
50
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: `${label}: started`});
|
51
|
+
try {
|
52
|
+
const result = await callback();
|
53
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: `${label}: done`});
|
54
|
+
return result;
|
55
|
+
} catch (e) {
|
56
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: `${label}: failed. ${e.message}`});
|
57
|
+
throw e;
|
58
|
+
}
|
59
|
+
},
|
60
|
+
check: async (message, ok) => {
|
61
|
+
const wasOk = await ok;
|
62
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: `${message}: ${wasOk}`});
|
63
|
+
},
|
64
|
+
start: (label) => {
|
65
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: label});
|
66
|
+
},
|
67
|
+
showValue: (label, value) => {
|
68
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: `${label}: ${value}`});
|
69
|
+
},
|
70
|
+
error: (msg, ...args) => {
|
71
|
+
socketManager.emit(`install`, 'install:log', {type: 'error', message: msg});
|
72
|
+
},
|
73
|
+
warn: (msg, ...args) => {
|
74
|
+
socketManager.emit(`install`, 'install:log', {type: 'warn', message: msg});
|
75
|
+
},
|
76
|
+
info: (msg, ...args) => {
|
77
|
+
socketManager.emit(`install`, 'install:log', {type: 'info', message: msg});
|
78
|
+
},
|
79
|
+
debug: (msg, ...args) => {
|
80
|
+
socketManager.emit(`install`, 'install:log', {type: 'debug', message: msg});
|
81
|
+
},
|
82
|
+
}
|
package/src/providerManager.js
CHANGED
@@ -1,14 +1,13 @@
|
|
1
|
-
const _ = require('lodash');
|
2
1
|
const FS = require('fs');
|
3
2
|
const Path = require('path');
|
4
|
-
const
|
5
|
-
|
3
|
+
const FSExtra = require('fs-extra');
|
4
|
+
const repositoryManager = require('./repositoryManager')
|
6
5
|
const ClusterConfiguration = require('@kapeta/local-cluster-config');
|
7
6
|
|
8
7
|
class ProviderManager {
|
9
8
|
|
10
9
|
constructor() {
|
11
|
-
this.
|
10
|
+
this._webAssetCache = {};
|
12
11
|
}
|
13
12
|
|
14
13
|
getWebProviders() {
|
@@ -17,54 +16,32 @@ class ProviderManager {
|
|
17
16
|
.filter((providerDefinition) => providerDefinition.hasWeb)
|
18
17
|
}
|
19
18
|
|
20
|
-
|
21
|
-
const
|
19
|
+
async getAsset(handle, name, version, sourceMap = false) {
|
20
|
+
const fullName = `${handle}/${name}`;
|
21
|
+
const id = `${handle}/${name}/${version}/web.js${sourceMap ? '.map' : ''}`;
|
22
|
+
|
23
|
+
if (this._webAssetCache[id] &&
|
24
|
+
await FSExtra.exists(this._webAssetCache[id])) {
|
25
|
+
return FSExtra.read(this._webAssetCache[id]);
|
26
|
+
}
|
22
27
|
|
23
|
-
|
24
|
-
webProviders.map((webProvider) => {
|
25
|
-
return Glob.sync('web/**/*.js', {cwd: webProvider.path}).map((file) => {
|
26
|
-
return {webProvider, file};
|
27
|
-
});
|
28
|
-
}).forEach((webFiles) => {
|
29
|
-
providerFiles.push(...webFiles);
|
30
|
-
});
|
28
|
+
await repositoryManager.ensureAsset(handle, name, version);
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
loadAssets() {
|
36
|
-
this.getWebAssets().forEach((asset) => {
|
37
|
-
const providerId = asset.webProvider.definition.metadata.name;
|
38
|
-
const file = asset.file;
|
39
|
-
const assetId = `${providerId}/${asset.webProvider.version}/${file}`;
|
40
|
-
this._assetCache[assetId] = Path.join(asset.webProvider.path, file);
|
30
|
+
const installedProvider = this.getWebProviders().find((providerDefinition) => {
|
31
|
+
return providerDefinition.definition.metadata.name === fullName &&
|
32
|
+
providerDefinition.version === version;
|
41
33
|
})
|
42
|
-
}
|
43
34
|
|
35
|
+
if (installedProvider) {
|
36
|
+
//Check locally installed providers
|
37
|
+
const path = Path.join(installedProvider.path, 'web', handle, `${name}.js${sourceMap ? '.map' : ''}`);
|
38
|
+
if (await FSExtra.exists(path)) {
|
39
|
+
this._webAssetCache[id] = path;
|
44
40
|
|
45
|
-
|
46
|
-
|
47
|
-
*
|
48
|
-
* Provides frontend / applications with the implementation of the frontends for the
|
49
|
-
* providers.
|
50
|
-
*
|
51
|
-
*/
|
52
|
-
getPublicJS() {
|
53
|
-
this.loadAssets();
|
54
|
-
const includes = Object.keys(this._assetCache).map((assetId) => {
|
55
|
-
return `${ClusterConfiguration.getClusterServiceAddress()}/providers/asset/${assetId}`
|
56
|
-
});
|
57
|
-
|
58
|
-
return `Kapeta.setPluginPaths(${JSON.stringify(includes)});`
|
59
|
-
}
|
60
|
-
|
61
|
-
getAsset(id) {
|
62
|
-
if (_.isEmpty(this._assetCache)) {
|
63
|
-
this.loadAssets();
|
64
|
-
}
|
65
|
-
if (this._assetCache[id]) {
|
66
|
-
return FS.readFileSync(this._assetCache[id]).toString();
|
41
|
+
return FSExtra.read(path);
|
42
|
+
}
|
67
43
|
}
|
44
|
+
|
68
45
|
return null;
|
69
46
|
}
|
70
47
|
}
|
package/src/providers/routes.js
CHANGED
@@ -5,16 +5,38 @@ const router = new Router();
|
|
5
5
|
|
6
6
|
router.use('/', require('../middleware/cors'));
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
router.get('/', async (req, res) => {
|
9
|
+
const result = await providerManager.getWebProviders();
|
10
|
+
|
11
|
+
res.send(result);
|
12
|
+
});
|
13
|
+
|
14
|
+
router.get('/asset/:handle/:name/:version/web.js', async (req, res) => {
|
15
|
+
|
16
|
+
const {handle, name, version} = req.params;
|
17
|
+
let result = await providerManager.getAsset(handle, name, version);
|
18
|
+
|
19
|
+
if (version !== 'local') {
|
20
|
+
res.setHeader('Cache-Control', 'max-age=31536000, immutable');
|
21
|
+
}
|
22
|
+
|
23
|
+
if (!result) {
|
24
|
+
res.status(404).send('');
|
25
|
+
} else {
|
26
|
+
res.send(result
|
27
|
+
.replace(`${name}.js.map`, 'web.js.map')
|
28
|
+
);
|
29
|
+
}
|
13
30
|
});
|
14
31
|
|
15
|
-
router.get('/asset
|
16
|
-
|
17
|
-
const
|
32
|
+
router.get('/asset/:handle/:name/:version/web.js.map', async (req, res) => {
|
33
|
+
|
34
|
+
const {handle, name, version} = req.params;
|
35
|
+
const result = await providerManager.getAsset(handle, name, version, true);
|
36
|
+
if (version !== 'local') {
|
37
|
+
res.setHeader('Cache-Control', 'max-age=31536000, immutable');
|
38
|
+
}
|
39
|
+
|
18
40
|
if (!result) {
|
19
41
|
res.status(404).send('');
|
20
42
|
} else {
|
@@ -0,0 +1,201 @@
|
|
1
|
+
const ClusterConfiguration = require("@kapeta/local-cluster-config");
|
2
|
+
const FS = require("node:fs");
|
3
|
+
const FSExtra = require("fs-extra");
|
4
|
+
const Path = require("node:path");
|
5
|
+
const socketManager = require("./socketManager");
|
6
|
+
const {Actions, RegistryService, Config} = require("@kapeta/nodejs-registry-utils");
|
7
|
+
const progressListener = require("./progressListener");
|
8
|
+
const os = require("os");
|
9
|
+
const {parseKapetaUri} = require("@kapeta/nodejs-utils");
|
10
|
+
const INSTALL_ATTEMPTED = {};
|
11
|
+
|
12
|
+
class RepositoryManager {
|
13
|
+
|
14
|
+
constructor() {
|
15
|
+
this.watcher = null;
|
16
|
+
this.changeEventsEnabled = true;
|
17
|
+
this.listenForChanges();
|
18
|
+
this._registryService = new RegistryService(
|
19
|
+
Config.data.registry.url
|
20
|
+
);
|
21
|
+
this._cache = {};
|
22
|
+
this._installQueue = [];
|
23
|
+
}
|
24
|
+
|
25
|
+
setChangeEventsEnabled(enabled) {
|
26
|
+
this.changeEventsEnabled = enabled;
|
27
|
+
}
|
28
|
+
|
29
|
+
listenForChanges() {
|
30
|
+
const baseDir = ClusterConfiguration.getRepositoryBasedir();
|
31
|
+
if (!FS.existsSync(baseDir)) {
|
32
|
+
FSExtra.mkdirpSync(baseDir);
|
33
|
+
}
|
34
|
+
|
35
|
+
let allDefinitions = ClusterConfiguration
|
36
|
+
.getDefinitions();
|
37
|
+
|
38
|
+
console.log('Watching local repository for provider changes: %s', baseDir);
|
39
|
+
try {
|
40
|
+
this.watcher = FS.watch(baseDir, { recursive: true });
|
41
|
+
} catch (e) {
|
42
|
+
// Fallback to run without watch mode due to potential platform issues.
|
43
|
+
// https://nodejs.org/docs/latest/api/fs.html#caveats
|
44
|
+
console.log('Unable to watch for changes. Changes to assets will not update automatically.');
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
this.watcher.on('change', (eventType, filename) => {
|
48
|
+
const [handle, name, version] = filename.split(/\//g);
|
49
|
+
if (!name || !version) {
|
50
|
+
return;
|
51
|
+
}
|
52
|
+
|
53
|
+
if (!this.changeEventsEnabled) {
|
54
|
+
return;
|
55
|
+
}
|
56
|
+
|
57
|
+
const ymlPath = Path.join(baseDir, handle, name, version, 'kapeta.yml');
|
58
|
+
const newDefinitions = ClusterConfiguration.getDefinitions();
|
59
|
+
|
60
|
+
const newDefinition = newDefinitions.find(d => d.ymlPath === ymlPath);
|
61
|
+
let currentDefinition = allDefinitions.find(d => d.ymlPath === ymlPath);
|
62
|
+
const ymlExists = FS.existsSync(ymlPath);
|
63
|
+
let type;
|
64
|
+
if (ymlExists) {
|
65
|
+
if (currentDefinition) {
|
66
|
+
type = 'updated';
|
67
|
+
} else if (newDefinition) {
|
68
|
+
type = 'added';
|
69
|
+
currentDefinition = newDefinition;
|
70
|
+
} else {
|
71
|
+
//Other definition was added / updated - ignore
|
72
|
+
return;
|
73
|
+
}
|
74
|
+
} else {
|
75
|
+
if (currentDefinition) {
|
76
|
+
const ref = parseKapetaUri(
|
77
|
+
`${currentDefinition.definition.metadata.name}:${currentDefinition.version}`
|
78
|
+
).id;
|
79
|
+
delete INSTALL_ATTEMPTED[ref];
|
80
|
+
//Something was removed
|
81
|
+
type = 'removed';
|
82
|
+
} else {
|
83
|
+
//Other definition was removed - ignore
|
84
|
+
return;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
const payload = {type, definition: currentDefinition?.definition, asset: {handle, name, version} };
|
89
|
+
|
90
|
+
allDefinitions = newDefinitions
|
91
|
+
socketManager.emit(`assets`, 'changed', payload);
|
92
|
+
});
|
93
|
+
}
|
94
|
+
|
95
|
+
stopListening() {
|
96
|
+
this.watcher.close();
|
97
|
+
this.watcher = null;
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
*
|
102
|
+
* @param {string[]} refs
|
103
|
+
* @return {Promise<void>}
|
104
|
+
* @private
|
105
|
+
*/
|
106
|
+
async _install(refs) {
|
107
|
+
//We make sure to only install one asset at a time - otherwise unexpected things might happen
|
108
|
+
const out = new Promise((resolve, reject) => {
|
109
|
+
this._installQueue.push(async () => {
|
110
|
+
try {
|
111
|
+
const normalizedRefs = refs.map(ref => parseKapetaUri(ref).id)
|
112
|
+
const filteredRefs = normalizedRefs.filter(ref => !INSTALL_ATTEMPTED[ref]);
|
113
|
+
if (filteredRefs.length > 0) {
|
114
|
+
filteredRefs.forEach(ref => INSTALL_ATTEMPTED[ref] = true);
|
115
|
+
//Auto-install missing asset
|
116
|
+
try {
|
117
|
+
//We change to a temp dir to avoid issues with the current working directory
|
118
|
+
process.chdir(os.tmpdir());
|
119
|
+
//Disable change events while installing
|
120
|
+
this.setChangeEventsEnabled(false);
|
121
|
+
socketManager.emit(`install`, 'install:action', {type: 'start', refs});
|
122
|
+
await Actions.install(progressListener, normalizedRefs, {});
|
123
|
+
socketManager.emit(`install`, 'install:action', {type: 'done', refs});
|
124
|
+
} catch (e) {
|
125
|
+
socketManager.emit(`install`, 'install:action', {type: 'failed', refs, error: e.message});
|
126
|
+
} finally {
|
127
|
+
this.setChangeEventsEnabled(true);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
resolve();
|
131
|
+
} catch (e) {
|
132
|
+
reject(e);
|
133
|
+
} finally {
|
134
|
+
this._processNext().catch(e => console.error(e));
|
135
|
+
}
|
136
|
+
})
|
137
|
+
});
|
138
|
+
|
139
|
+
this._processNext().catch(e => console.error(e));
|
140
|
+
|
141
|
+
return out;
|
142
|
+
}
|
143
|
+
|
144
|
+
async _processNext() {
|
145
|
+
if (this._processing) {
|
146
|
+
return;
|
147
|
+
}
|
148
|
+
this._processing = true;
|
149
|
+
try {
|
150
|
+
while (this._installQueue.length > 0) {
|
151
|
+
const item = this._installQueue.shift();
|
152
|
+
|
153
|
+
await item();
|
154
|
+
}
|
155
|
+
} finally {
|
156
|
+
this._processing = false;
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
async ensureAsset(handle, name, version) {
|
161
|
+
const fullName = `${handle}/${name}`;
|
162
|
+
const ref = `${fullName}:${version}`;
|
163
|
+
|
164
|
+
if (version === 'local') {
|
165
|
+
//TODO: Get dependencies for local asset
|
166
|
+
return null;
|
167
|
+
}
|
168
|
+
|
169
|
+
const installedAsset = ClusterConfiguration.getDefinitions().find(d =>
|
170
|
+
d.definition.metadata.name === fullName &&
|
171
|
+
d.version === version);
|
172
|
+
|
173
|
+
|
174
|
+
if (installedAsset && this._cache[ref] === true) {
|
175
|
+
return;
|
176
|
+
}
|
177
|
+
|
178
|
+
if (!installedAsset && this._cache[ref] === false) {
|
179
|
+
return;
|
180
|
+
}
|
181
|
+
|
182
|
+
const assetVersion = await this._registryService.getVersion(fullName, version);
|
183
|
+
if (!assetVersion) {
|
184
|
+
this._cache[ref] = false;
|
185
|
+
return;
|
186
|
+
}
|
187
|
+
|
188
|
+
this._cache[ref] = true;
|
189
|
+
if (!installedAsset) {
|
190
|
+
await this._install([ref]);
|
191
|
+
} else {
|
192
|
+
//Ensure dependencies are installed
|
193
|
+
const refs = assetVersion.dependencies.map((dep) => dep.name);
|
194
|
+
await this._install(refs);
|
195
|
+
}
|
196
|
+
|
197
|
+
|
198
|
+
}
|
199
|
+
}
|
200
|
+
|
201
|
+
module.exports = new RepositoryManager();
|