@kapeta/local-cluster-service 0.6.1 → 0.7.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/.eslintrc.cjs +17 -0
- package/.github/workflows/main.yml +22 -22
- package/.prettierignore +4 -0
- package/.vscode/launch.json +2 -4
- package/CHANGELOG.md +7 -0
- package/definitions.d.ts +17 -35
- package/dist/cjs/index.d.ts +27 -0
- package/dist/cjs/index.js +126 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/src/assetManager.d.ts +31 -0
- package/dist/cjs/src/assetManager.js +153 -0
- package/dist/cjs/src/assets/routes.d.ts +3 -0
- package/dist/cjs/src/assets/routes.js +117 -0
- package/dist/cjs/src/clusterService.d.ts +40 -0
- package/dist/cjs/src/clusterService.js +114 -0
- package/dist/cjs/src/codeGeneratorManager.d.ts +8 -0
- package/dist/cjs/src/codeGeneratorManager.js +53 -0
- package/dist/cjs/src/config/routes.d.ts +3 -0
- package/dist/cjs/src/config/routes.js +126 -0
- package/dist/cjs/src/configManager.d.ts +36 -0
- package/dist/cjs/src/configManager.js +110 -0
- package/dist/cjs/src/containerManager.d.ts +89 -0
- package/dist/cjs/src/containerManager.js +365 -0
- package/dist/cjs/src/filesystem/routes.d.ts +3 -0
- package/dist/cjs/src/filesystem/routes.js +69 -0
- package/dist/cjs/src/filesystemManager.d.ts +15 -0
- package/dist/cjs/src/filesystemManager.js +87 -0
- package/dist/cjs/src/identities/routes.d.ts +3 -0
- package/dist/cjs/src/identities/routes.js +18 -0
- package/dist/cjs/src/instanceManager.d.ts +56 -0
- package/dist/cjs/src/instanceManager.js +424 -0
- package/dist/cjs/src/instances/routes.d.ts +3 -0
- package/dist/cjs/src/instances/routes.js +134 -0
- package/dist/cjs/src/middleware/cors.d.ts +2 -0
- package/dist/cjs/src/middleware/cors.js +10 -0
- package/dist/cjs/src/middleware/kapeta.d.ts +11 -0
- package/dist/cjs/src/middleware/kapeta.js +17 -0
- package/dist/cjs/src/middleware/stringBody.d.ts +5 -0
- package/dist/cjs/src/middleware/stringBody.js +14 -0
- package/dist/cjs/src/networkManager.d.ts +32 -0
- package/dist/cjs/src/networkManager.js +109 -0
- package/dist/cjs/src/operatorManager.d.ts +36 -0
- package/dist/cjs/src/operatorManager.js +165 -0
- package/dist/cjs/src/progressListener.d.ts +20 -0
- package/dist/cjs/src/progressListener.js +91 -0
- package/dist/cjs/src/providerManager.d.ts +9 -0
- package/dist/cjs/src/providerManager.js +51 -0
- package/dist/cjs/src/providers/routes.d.ts +3 -0
- package/dist/cjs/src/providers/routes.js +42 -0
- package/dist/cjs/src/proxy/routes.d.ts +3 -0
- package/dist/cjs/src/proxy/routes.js +111 -0
- package/dist/cjs/src/proxy/types/rest.d.ts +4 -0
- package/dist/cjs/src/proxy/types/rest.js +114 -0
- package/dist/cjs/src/proxy/types/web.d.ts +4 -0
- package/dist/cjs/src/proxy/types/web.js +53 -0
- package/dist/cjs/src/repositoryManager.d.ts +17 -0
- package/dist/cjs/src/repositoryManager.js +215 -0
- package/dist/cjs/src/serviceManager.d.ts +29 -0
- package/dist/cjs/src/serviceManager.js +99 -0
- package/dist/cjs/src/socketManager.d.ts +14 -0
- package/dist/cjs/src/socketManager.js +53 -0
- package/dist/cjs/src/storageService.d.ts +17 -0
- package/dist/cjs/src/storageService.js +74 -0
- package/dist/cjs/src/traffic/routes.d.ts +3 -0
- package/dist/cjs/src/traffic/routes.js +18 -0
- package/dist/cjs/src/types.d.ts +88 -0
- package/dist/cjs/src/types.js +2 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.d.ts +29 -0
- package/dist/cjs/src/utils/BlockInstanceRunner.js +468 -0
- package/dist/cjs/src/utils/LogData.d.ts +19 -0
- package/dist/cjs/src/utils/LogData.js +43 -0
- package/dist/cjs/src/utils/pathTemplateParser.d.ts +26 -0
- package/dist/cjs/src/utils/pathTemplateParser.js +121 -0
- package/dist/cjs/src/utils/utils.d.ts +1 -0
- package/dist/cjs/src/utils/utils.js +18 -0
- package/dist/cjs/start.d.ts +1 -0
- package/dist/cjs/start.js +12 -0
- package/dist/esm/index.d.ts +27 -0
- package/dist/esm/index.js +121 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/src/assetManager.d.ts +31 -0
- package/{src → dist/esm/src}/assetManager.js +22 -60
- package/dist/esm/src/assets/routes.d.ts +3 -0
- package/{src → dist/esm/src}/assets/routes.js +21 -36
- package/dist/esm/src/clusterService.d.ts +40 -0
- package/{src → dist/esm/src}/clusterService.js +14 -37
- package/dist/esm/src/codeGeneratorManager.d.ts +8 -0
- package/{src → dist/esm/src}/codeGeneratorManager.js +15 -24
- package/dist/esm/src/config/routes.d.ts +3 -0
- package/{src → dist/esm/src}/config/routes.js +40 -89
- package/dist/esm/src/configManager.d.ts +36 -0
- package/{src → dist/esm/src}/configManager.js +11 -40
- package/dist/esm/src/containerManager.d.ts +89 -0
- package/{src → dist/esm/src}/containerManager.js +81 -182
- package/dist/esm/src/filesystem/routes.d.ts +3 -0
- package/dist/esm/src/filesystem/routes.js +64 -0
- package/dist/esm/src/filesystemManager.d.ts +15 -0
- package/{src → dist/esm/src}/filesystemManager.js +20 -28
- package/dist/esm/src/identities/routes.d.ts +3 -0
- package/dist/esm/src/identities/routes.js +13 -0
- package/dist/esm/src/instanceManager.d.ts +56 -0
- package/{src → dist/esm/src}/instanceManager.js +88 -179
- package/dist/esm/src/instances/routes.d.ts +3 -0
- package/{src → dist/esm/src}/instances/routes.js +31 -70
- package/dist/esm/src/middleware/cors.d.ts +2 -0
- package/{src → dist/esm/src}/middleware/cors.js +2 -3
- package/dist/esm/src/middleware/kapeta.d.ts +11 -0
- package/{src → dist/esm/src}/middleware/kapeta.js +3 -7
- package/dist/esm/src/middleware/stringBody.d.ts +5 -0
- package/{src → dist/esm/src}/middleware/stringBody.js +2 -3
- package/dist/esm/src/networkManager.d.ts +32 -0
- package/{src → dist/esm/src}/networkManager.js +16 -33
- package/dist/esm/src/operatorManager.d.ts +36 -0
- package/{src → dist/esm/src}/operatorManager.js +35 -91
- package/dist/esm/src/progressListener.d.ts +20 -0
- package/dist/esm/src/progressListener.js +88 -0
- package/dist/esm/src/providerManager.d.ts +9 -0
- package/dist/esm/src/providerManager.js +45 -0
- package/dist/esm/src/providers/routes.d.ts +3 -0
- package/{src → dist/esm/src}/providers/routes.js +10 -16
- package/dist/esm/src/proxy/routes.d.ts +3 -0
- package/dist/esm/src/proxy/routes.js +106 -0
- package/dist/esm/src/proxy/types/rest.d.ts +4 -0
- package/dist/esm/src/proxy/types/rest.js +107 -0
- package/dist/esm/src/proxy/types/web.d.ts +4 -0
- package/{src → dist/esm/src}/proxy/types/web.js +13 -35
- package/dist/esm/src/repositoryManager.d.ts +17 -0
- package/dist/esm/src/repositoryManager.js +209 -0
- package/dist/esm/src/serviceManager.d.ts +29 -0
- package/{src → dist/esm/src}/serviceManager.js +12 -42
- package/dist/esm/src/socketManager.d.ts +14 -0
- package/{src → dist/esm/src}/socketManager.js +19 -23
- package/dist/esm/src/storageService.d.ts +17 -0
- package/{src → dist/esm/src}/storageService.js +8 -27
- package/dist/esm/src/traffic/routes.d.ts +3 -0
- package/{src → dist/esm/src}/traffic/routes.js +4 -9
- package/dist/esm/src/types.d.ts +88 -0
- package/dist/esm/src/types.js +1 -0
- package/dist/esm/src/utils/BlockInstanceRunner.d.ts +29 -0
- package/{src → dist/esm/src}/utils/BlockInstanceRunner.js +137 -256
- package/dist/esm/src/utils/LogData.d.ts +19 -0
- package/{src → dist/esm/src}/utils/LogData.js +11 -22
- package/dist/esm/src/utils/pathTemplateParser.d.ts +26 -0
- package/{src → dist/esm/src}/utils/pathTemplateParser.js +21 -40
- package/dist/esm/src/utils/utils.d.ts +1 -0
- package/dist/esm/src/utils/utils.js +11 -0
- package/dist/esm/start.d.ts +1 -0
- package/dist/esm/start.js +7 -0
- package/index.ts +147 -0
- package/package.json +106 -74
- package/src/assetManager.ts +191 -0
- package/src/assets/routes.ts +132 -0
- package/src/clusterService.ts +134 -0
- package/src/codeGeneratorManager.ts +57 -0
- package/src/config/routes.ts +159 -0
- package/src/configManager.ts +148 -0
- package/src/containerManager.ts +466 -0
- package/src/filesystem/routes.ts +74 -0
- package/src/filesystemManager.ts +93 -0
- package/src/identities/routes.ts +20 -0
- package/src/instanceManager.ts +503 -0
- package/src/instances/routes.ts +164 -0
- package/src/middleware/cors.ts +9 -0
- package/src/middleware/kapeta.ts +27 -0
- package/src/middleware/stringBody.ts +16 -0
- package/src/networkManager.ts +137 -0
- package/src/operatorManager.ts +221 -0
- package/src/progressListener.ts +102 -0
- package/src/{providerManager.js → providerManager.ts} +15 -31
- package/src/providers/routes.ts +46 -0
- package/src/proxy/routes.ts +148 -0
- package/src/proxy/types/{rest.js → rest.ts} +30 -30
- package/src/proxy/types/web.ts +60 -0
- package/src/{repositoryManager.js → repositoryManager.ts} +45 -73
- package/src/serviceManager.ts +120 -0
- package/src/socketManager.ts +57 -0
- package/src/storageService.ts +88 -0
- package/src/traffic/routes.ts +18 -0
- package/src/types.ts +97 -0
- package/src/utils/BlockInstanceRunner.ts +555 -0
- package/src/utils/LogData.ts +47 -0
- package/src/utils/pathTemplateParser.ts +138 -0
- package/src/utils/utils.ts +12 -0
- package/start.ts +8 -0
- package/tsconfig.json +13 -0
- package/index.js +0 -127
- package/src/filesystem/routes.js +0 -74
- package/src/identities/routes.js +0 -19
- package/src/progressListener.js +0 -82
- package/src/proxy/routes.js +0 -126
- package/src/utils/utils.js +0 -13
- package/start.js +0 -7
@@ -0,0 +1,191 @@
|
|
1
|
+
import Path from 'node:path';
|
2
|
+
import FS from 'node:fs';
|
3
|
+
import FSExtra from 'fs-extra';
|
4
|
+
import YAML from 'yaml';
|
5
|
+
import NodeCache from 'node-cache';
|
6
|
+
import ClusterConfiguration, { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
|
7
|
+
import { codeGeneratorManager } from './codeGeneratorManager';
|
8
|
+
import { progressListener } from './progressListener';
|
9
|
+
import { parseKapetaUri } from '@kapeta/nodejs-utils';
|
10
|
+
import { repositoryManager } from './repositoryManager';
|
11
|
+
import { BlockDefinition } from '@kapeta/schemas';
|
12
|
+
import { Actions } from '@kapeta/nodejs-registry-utils';
|
13
|
+
|
14
|
+
export interface EnrichedAsset {
|
15
|
+
ref: string;
|
16
|
+
editable: boolean;
|
17
|
+
exists: boolean;
|
18
|
+
version: string;
|
19
|
+
kind: string;
|
20
|
+
data: Definition;
|
21
|
+
path: string;
|
22
|
+
ymlPath: string;
|
23
|
+
}
|
24
|
+
|
25
|
+
function enrichAsset(asset: DefinitionInfo): EnrichedAsset {
|
26
|
+
return {
|
27
|
+
ref: `kapeta://${asset.definition.metadata.name}:${asset.version}`,
|
28
|
+
editable: asset.version === 'local', //Only local versions are editable
|
29
|
+
exists: true,
|
30
|
+
version: asset.version,
|
31
|
+
kind: asset.definition.kind,
|
32
|
+
data: asset.definition,
|
33
|
+
path: asset.path,
|
34
|
+
ymlPath: asset.ymlPath,
|
35
|
+
};
|
36
|
+
}
|
37
|
+
|
38
|
+
function compareRefs(a: string, b: string) {
|
39
|
+
const [aProtocol, aId] = parseRef(a);
|
40
|
+
const [bProtocol, bId] = parseRef(b);
|
41
|
+
|
42
|
+
return aProtocol === bProtocol && aId === bId;
|
43
|
+
}
|
44
|
+
|
45
|
+
function parseRef(ref: string) {
|
46
|
+
let out = ref.split(/:\/\//, 2);
|
47
|
+
|
48
|
+
if (out.length === 1) {
|
49
|
+
return ['kapeta', ref.toLowerCase()];
|
50
|
+
}
|
51
|
+
return [out[0].toLowerCase(), out[1].toLowerCase()];
|
52
|
+
}
|
53
|
+
|
54
|
+
class AssetManager {
|
55
|
+
private cache: NodeCache;
|
56
|
+
|
57
|
+
constructor() {
|
58
|
+
this.cache = new NodeCache({
|
59
|
+
stdTTL: 60 * 60, // 1 hour
|
60
|
+
});
|
61
|
+
}
|
62
|
+
|
63
|
+
/**
|
64
|
+
*
|
65
|
+
* @param {string[]} [assetKinds]
|
66
|
+
* @returns {{path: *, ref: string, data: *, editable: boolean, kind: *, exists: boolean}[]}
|
67
|
+
*/
|
68
|
+
getAssets(assetKinds?: string[]): EnrichedAsset[] {
|
69
|
+
if (!assetKinds) {
|
70
|
+
const blockTypeProviders = ClusterConfiguration.getDefinitions([
|
71
|
+
'core/block-type',
|
72
|
+
'core/block-type-operator',
|
73
|
+
]);
|
74
|
+
assetKinds = blockTypeProviders.map((p) => {
|
75
|
+
return `${p.definition.metadata.name}:${p.version}`;
|
76
|
+
});
|
77
|
+
assetKinds.push('core/plan');
|
78
|
+
}
|
79
|
+
|
80
|
+
const assets = ClusterConfiguration.getDefinitions(assetKinds);
|
81
|
+
|
82
|
+
return assets.map(enrichAsset);
|
83
|
+
}
|
84
|
+
|
85
|
+
getPlans(): EnrichedAsset[] {
|
86
|
+
return this.getAssets(['core/plan']);
|
87
|
+
}
|
88
|
+
|
89
|
+
async getPlan(ref: string, noCache: boolean = false) {
|
90
|
+
const asset = await this.getAsset(ref, noCache);
|
91
|
+
|
92
|
+
if ('core/plan' !== asset?.kind) {
|
93
|
+
throw new Error('Asset was not a plan: ' + ref);
|
94
|
+
}
|
95
|
+
|
96
|
+
return asset.data;
|
97
|
+
}
|
98
|
+
|
99
|
+
async getAsset(ref: string, noCache: boolean = false): Promise<EnrichedAsset | undefined> {
|
100
|
+
const cacheKey = `getAsset:${ref}`;
|
101
|
+
if (!noCache && this.cache.has(cacheKey)) {
|
102
|
+
return this.cache.get(cacheKey);
|
103
|
+
}
|
104
|
+
const uri = parseKapetaUri(ref);
|
105
|
+
await repositoryManager.ensureAsset(uri.handle, uri.name, uri.version);
|
106
|
+
|
107
|
+
let asset = ClusterConfiguration.getDefinitions()
|
108
|
+
.map(enrichAsset)
|
109
|
+
.find((a) => parseKapetaUri(a.ref).equals(uri));
|
110
|
+
|
111
|
+
if (!asset) {
|
112
|
+
throw new Error('Asset not found: ' + ref);
|
113
|
+
}
|
114
|
+
this.cache.set(cacheKey, asset);
|
115
|
+
return asset;
|
116
|
+
}
|
117
|
+
|
118
|
+
async createAsset(path: string, yaml: BlockDefinition): Promise<EnrichedAsset[]> {
|
119
|
+
if (FS.existsSync(path)) {
|
120
|
+
throw new Error('File already exists: ' + path);
|
121
|
+
}
|
122
|
+
|
123
|
+
const dirName = Path.dirname(path);
|
124
|
+
if (!FS.existsSync(dirName)) {
|
125
|
+
FSExtra.mkdirpSync(dirName);
|
126
|
+
}
|
127
|
+
|
128
|
+
FS.writeFileSync(path, YAML.stringify(yaml));
|
129
|
+
|
130
|
+
const asset = await this.importFile(path);
|
131
|
+
|
132
|
+
if (codeGeneratorManager.canGenerateCode(yaml)) {
|
133
|
+
await codeGeneratorManager.generate(path, yaml);
|
134
|
+
}
|
135
|
+
this.cache.flushAll();
|
136
|
+
return asset;
|
137
|
+
}
|
138
|
+
|
139
|
+
async updateAsset(ref: string, yaml: BlockDefinition) {
|
140
|
+
const asset = await this.getAsset(ref, true);
|
141
|
+
if (!asset) {
|
142
|
+
throw new Error('Attempted to update unknown asset: ' + ref);
|
143
|
+
}
|
144
|
+
|
145
|
+
if (!asset.editable) {
|
146
|
+
throw new Error('Attempted to update read-only asset: ' + ref);
|
147
|
+
}
|
148
|
+
|
149
|
+
if (!asset.ymlPath) {
|
150
|
+
throw new Error('Attempted to update corrupted asset: ' + ref);
|
151
|
+
}
|
152
|
+
|
153
|
+
FS.writeFileSync(asset.ymlPath, YAML.stringify(yaml));
|
154
|
+
this.cache.flushAll();
|
155
|
+
if (codeGeneratorManager.canGenerateCode(yaml)) {
|
156
|
+
await codeGeneratorManager.generate(asset.ymlPath, yaml);
|
157
|
+
} else {
|
158
|
+
console.log('Could not generate code for %s', yaml.kind ? yaml.kind : 'unknown yaml');
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
async importFile(filePath: string) {
|
163
|
+
if (filePath.startsWith('file://')) {
|
164
|
+
filePath = filePath.substring('file://'.length);
|
165
|
+
}
|
166
|
+
|
167
|
+
if (!FS.existsSync(filePath)) {
|
168
|
+
throw new Error('File not found: ' + filePath);
|
169
|
+
}
|
170
|
+
|
171
|
+
const assetInfos = YAML.parseAllDocuments(FS.readFileSync(filePath).toString()).map((doc) => doc.toJSON());
|
172
|
+
|
173
|
+
await Actions.link(progressListener, Path.dirname(filePath));
|
174
|
+
|
175
|
+
const version = 'local';
|
176
|
+
const refs = assetInfos.map((assetInfo) => `kapeta://${assetInfo.metadata.name}:${version}`);
|
177
|
+
this.cache.flushAll();
|
178
|
+
return this.getAssets().filter((a) => refs.some((ref) => compareRefs(ref, a.ref)));
|
179
|
+
}
|
180
|
+
|
181
|
+
async unregisterAsset(ref: string) {
|
182
|
+
const asset = await this.getAsset(ref, true);
|
183
|
+
if (!asset) {
|
184
|
+
throw new Error('Asset does not exists: ' + ref);
|
185
|
+
}
|
186
|
+
this.cache.flushAll();
|
187
|
+
await Actions.uninstall(progressListener, [asset.ref]);
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
export const assetManager = new AssetManager();
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import Router from 'express-promise-router';
|
2
|
+
import { Request, Response } from 'express';
|
3
|
+
import YAML from 'yaml';
|
4
|
+
import { assetManager } from '../assetManager';
|
5
|
+
|
6
|
+
import { corsHandler } from '../middleware/cors';
|
7
|
+
|
8
|
+
import { stringBody, StringBodyRequest } from '../middleware/stringBody';
|
9
|
+
|
10
|
+
function parseBody(req: StringBodyRequest) {
|
11
|
+
switch (req.headers['content-type']) {
|
12
|
+
case 'application/json':
|
13
|
+
case 'application/x-json':
|
14
|
+
case 'text/json':
|
15
|
+
return JSON.parse(req.stringBody ?? '{}');
|
16
|
+
|
17
|
+
case 'application/yaml':
|
18
|
+
case 'application/x-yaml':
|
19
|
+
case 'text/yaml':
|
20
|
+
case 'text/x-yaml':
|
21
|
+
default:
|
22
|
+
return YAML.parse(req.stringBody ?? '{}');
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
const router = Router();
|
27
|
+
|
28
|
+
router.use('/', corsHandler);
|
29
|
+
router.use('/', stringBody);
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Get all local assets available
|
33
|
+
*/
|
34
|
+
router.get('/', (req: Request, res: Response) => {
|
35
|
+
res.send(assetManager.getAssets());
|
36
|
+
});
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Get single asset
|
40
|
+
*/
|
41
|
+
router.get('/read', async (req: Request, res: Response) => {
|
42
|
+
if (!req.query.ref) {
|
43
|
+
res.status(400).send({ error: 'Query parameter "ref" is missing' });
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
|
47
|
+
try {
|
48
|
+
res.send(await assetManager.getAsset(req.query.ref as string, true));
|
49
|
+
} catch (err: any) {
|
50
|
+
res.status(400).send({ error: err.message });
|
51
|
+
}
|
52
|
+
});
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Creates a new local file and registers it as an asset
|
56
|
+
*/
|
57
|
+
router.post('/create', async (req: Request, res: Response) => {
|
58
|
+
if (!req.query.path) {
|
59
|
+
res.status(400).send({ error: 'Query parameter "path" is missing' });
|
60
|
+
return;
|
61
|
+
}
|
62
|
+
|
63
|
+
const content = parseBody(req);
|
64
|
+
|
65
|
+
try {
|
66
|
+
const assets = await assetManager.createAsset(req.query.path as string, content);
|
67
|
+
|
68
|
+
res.status(200).send(assets);
|
69
|
+
} catch (err: any) {
|
70
|
+
console.log('Failed while creating asset', req.query.path, err.message);
|
71
|
+
res.status(400).send({ error: err.message });
|
72
|
+
}
|
73
|
+
});
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Updates reference with new content
|
77
|
+
*/
|
78
|
+
router.put('/update', async (req: Request, res: Response) => {
|
79
|
+
if (!req.query.ref) {
|
80
|
+
res.status(400).send({ error: 'Query parameter "ref" is missing' });
|
81
|
+
return;
|
82
|
+
}
|
83
|
+
|
84
|
+
const content = parseBody(req);
|
85
|
+
|
86
|
+
try {
|
87
|
+
await assetManager.updateAsset(req.query.ref as string, content);
|
88
|
+
|
89
|
+
res.sendStatus(204);
|
90
|
+
} catch (err: any) {
|
91
|
+
console.log('Failed while updating asset', req.query.ref, err.message);
|
92
|
+
res.status(400).send({ error: err.message });
|
93
|
+
}
|
94
|
+
});
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Unregisters an asset (doesn't delete the asset)
|
98
|
+
*/
|
99
|
+
router.delete('/', async (req: Request, res: Response) => {
|
100
|
+
if (!req.query.ref) {
|
101
|
+
res.status(400).send({ error: 'Query parameter "ref" is missing' });
|
102
|
+
return;
|
103
|
+
}
|
104
|
+
|
105
|
+
try {
|
106
|
+
await assetManager.unregisterAsset(req.query.ref as string);
|
107
|
+
|
108
|
+
res.status(204).send();
|
109
|
+
} catch (err: any) {
|
110
|
+
res.status(400).send({ error: err.message });
|
111
|
+
}
|
112
|
+
});
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Registers an existing file as an asset
|
116
|
+
*/
|
117
|
+
router.put('/import', async (req: Request, res: Response) => {
|
118
|
+
if (!req.query.ref) {
|
119
|
+
res.status(400).send({ error: 'Query parameter "ref" is missing' });
|
120
|
+
return;
|
121
|
+
}
|
122
|
+
|
123
|
+
try {
|
124
|
+
const assets = await assetManager.importFile(req.query.ref as string);
|
125
|
+
|
126
|
+
res.status(200).send(assets);
|
127
|
+
} catch (err: any) {
|
128
|
+
res.status(400).send({ error: err.message });
|
129
|
+
}
|
130
|
+
});
|
131
|
+
|
132
|
+
export default router;
|
@@ -0,0 +1,134 @@
|
|
1
|
+
const net = require('net');
|
2
|
+
const DEFAULT_SERVER_PORT = 35100;
|
3
|
+
const DEFAULT_START_PORT = 40000;
|
4
|
+
const DEFAULT_HOST = '127.0.0.1';
|
5
|
+
|
6
|
+
class ClusterService {
|
7
|
+
private _port: number;
|
8
|
+
private _currentPort: number;
|
9
|
+
private _initialized: boolean;
|
10
|
+
private _reservedPorts: number[];
|
11
|
+
private _host: string;
|
12
|
+
|
13
|
+
constructor() {
|
14
|
+
this._port = DEFAULT_SERVER_PORT;
|
15
|
+
this._currentPort = DEFAULT_START_PORT;
|
16
|
+
this._initialized = false;
|
17
|
+
this._reservedPorts = [];
|
18
|
+
this._host = DEFAULT_HOST;
|
19
|
+
}
|
20
|
+
|
21
|
+
reservePort(port: number | string) {
|
22
|
+
const intPort = parseInt(port as string);
|
23
|
+
if (this._reservedPorts.indexOf(intPort) > -1) {
|
24
|
+
throw new Error('Port already reserved: ' + intPort);
|
25
|
+
}
|
26
|
+
|
27
|
+
this._reservedPorts.push(intPort);
|
28
|
+
}
|
29
|
+
|
30
|
+
async init() {
|
31
|
+
if (this._initialized) {
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
|
35
|
+
this._initialized = true;
|
36
|
+
await this._findClusterServicePort();
|
37
|
+
}
|
38
|
+
|
39
|
+
async _findClusterServicePort() {
|
40
|
+
while (true) {
|
41
|
+
const isUsed = await this._checkIfPortIsUsed(this._port);
|
42
|
+
if (!isUsed) {
|
43
|
+
break;
|
44
|
+
}
|
45
|
+
|
46
|
+
this._port++;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Gets next available port
|
52
|
+
* @return {Promise<number>}
|
53
|
+
*/
|
54
|
+
async getNextAvailablePort() {
|
55
|
+
while (true) {
|
56
|
+
while (this._reservedPorts.indexOf(this._currentPort) > -1) {
|
57
|
+
this._currentPort++;
|
58
|
+
}
|
59
|
+
|
60
|
+
const nextPort = this._currentPort++;
|
61
|
+
const isUsed = await this._checkIfPortIsUsed(nextPort);
|
62
|
+
if (!isUsed) {
|
63
|
+
return nextPort;
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
_checkIfPortIsUsed(port: number, host: string = this._host) {
|
69
|
+
return new Promise((resolve, reject) => {
|
70
|
+
const server = net.createServer();
|
71
|
+
|
72
|
+
server.once('error', function (err: any) {
|
73
|
+
if (err.code === 'EADDRINUSE') {
|
74
|
+
server.close();
|
75
|
+
resolve(true);
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
|
79
|
+
server.close();
|
80
|
+
reject(err);
|
81
|
+
});
|
82
|
+
|
83
|
+
server.once('listening', function () {
|
84
|
+
server.close();
|
85
|
+
resolve(false);
|
86
|
+
});
|
87
|
+
|
88
|
+
server.listen(port, host);
|
89
|
+
});
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* The port of this local cluster service itself
|
94
|
+
*/
|
95
|
+
getClusterServicePort() {
|
96
|
+
return this._port;
|
97
|
+
}
|
98
|
+
|
99
|
+
/*
|
100
|
+
*Gets the host name ( 127.0.0.1 ) on which Express JS is listening
|
101
|
+
*/
|
102
|
+
getClusterServiceHost() {
|
103
|
+
return this._host;
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Set the port to be used for this local service
|
108
|
+
* @param port
|
109
|
+
*/
|
110
|
+
setClusterServicePort(port: number) {
|
111
|
+
this._port = port;
|
112
|
+
}
|
113
|
+
|
114
|
+
setClusterServiceHost(host: string) {
|
115
|
+
this._host = host;
|
116
|
+
}
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Gets that proxy path of a given request
|
120
|
+
*
|
121
|
+
* @param systemId
|
122
|
+
* @param consumerInstanceId
|
123
|
+
* @param consumerResourceName
|
124
|
+
* @param portType
|
125
|
+
* @return {string}
|
126
|
+
*/
|
127
|
+
getProxyPath(systemId: string, consumerInstanceId: string, consumerResourceName: string, portType: string) {
|
128
|
+
return `/proxy/${encodeURIComponent(systemId)}/${encodeURIComponent(consumerInstanceId)}/${encodeURIComponent(
|
129
|
+
consumerResourceName
|
130
|
+
)}/${encodeURIComponent(portType)}/`;
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
export const clusterService = new ClusterService();
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import Path from 'path';
|
2
|
+
import { registry as Targets, BlockCodeGenerator, CodeWriter } from '@kapeta/codegen';
|
3
|
+
import ClusterConfiguration from '@kapeta/local-cluster-config';
|
4
|
+
import { BlockDefinition } from '@kapeta/schemas';
|
5
|
+
|
6
|
+
const TARGET_KIND = 'core/language-target';
|
7
|
+
const BLOCK_TYPE_KIND = 'core/block-type';
|
8
|
+
|
9
|
+
class CodeGeneratorManager {
|
10
|
+
async reload() {
|
11
|
+
Targets.reset();
|
12
|
+
const languageTargets = ClusterConfiguration.getDefinitions(TARGET_KIND);
|
13
|
+
for (const languageTarget of languageTargets) {
|
14
|
+
const key = `${languageTarget.definition.metadata.name}:${languageTarget.version}`;
|
15
|
+
try {
|
16
|
+
const target = require(languageTarget.path);
|
17
|
+
if (target.default) {
|
18
|
+
Targets.register(key, target.default);
|
19
|
+
} else {
|
20
|
+
Targets.register(key, target);
|
21
|
+
}
|
22
|
+
} catch (e) {
|
23
|
+
console.error('Failed to load target: %s', key, e);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
canGenerateCode(yamlContent: BlockDefinition): boolean {
|
29
|
+
if (!yamlContent.spec.target?.kind) {
|
30
|
+
//Not all block types have targets
|
31
|
+
return false;
|
32
|
+
}
|
33
|
+
|
34
|
+
const blockTypes = ClusterConfiguration.getDefinitions(BLOCK_TYPE_KIND);
|
35
|
+
const blockTypeKinds = blockTypes.map(
|
36
|
+
(blockType) => blockType.definition.metadata.name.toLowerCase() + ':' + blockType.version
|
37
|
+
);
|
38
|
+
return !!(yamlContent && yamlContent.kind && blockTypeKinds.indexOf(yamlContent.kind.toLowerCase()) > -1);
|
39
|
+
}
|
40
|
+
|
41
|
+
async generate(yamlFile: string, yamlContent: BlockDefinition) {
|
42
|
+
const baseDir = Path.dirname(yamlFile);
|
43
|
+
console.log('Generating code for path: %s', baseDir);
|
44
|
+
const codeGenerator = new BlockCodeGenerator(yamlContent);
|
45
|
+
|
46
|
+
const output = await codeGenerator.generate();
|
47
|
+
const writer = new CodeWriter(baseDir, {});
|
48
|
+
const assets = writer.write(output);
|
49
|
+
|
50
|
+
await codeGenerator.postprocess(baseDir, assets);
|
51
|
+
|
52
|
+
console.log('Code generated for path: %s', baseDir);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
export const codeGeneratorManager = new CodeGeneratorManager();
|
57
|
+
codeGeneratorManager.reload();
|
@@ -0,0 +1,159 @@
|
|
1
|
+
import Router from 'express-promise-router';
|
2
|
+
import { configManager } from '../configManager';
|
3
|
+
import { serviceManager } from '../serviceManager';
|
4
|
+
import { operatorManager } from '../operatorManager';
|
5
|
+
import { instanceManager } from '../instanceManager';
|
6
|
+
import { StringBodyRequest } from '../middleware/stringBody';
|
7
|
+
import { corsHandler } from '../middleware/cors';
|
8
|
+
import { kapetaHeaders, KapetaRequest } from '../middleware/kapeta';
|
9
|
+
import { stringBody } from '../middleware/stringBody';
|
10
|
+
import { EnvironmentType, KapetaBodyRequest } from '../types';
|
11
|
+
import { Response } from 'express';
|
12
|
+
|
13
|
+
const router = Router();
|
14
|
+
const SYSTEM_ID = '$plan';
|
15
|
+
|
16
|
+
router.use('/', corsHandler);
|
17
|
+
router.use('/', kapetaHeaders);
|
18
|
+
router.use('/', stringBody);
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Returns the full configuration for a given service.
|
22
|
+
*/
|
23
|
+
router.get('/instance', (req: KapetaBodyRequest, res: Response) => {
|
24
|
+
const config = req.kapeta!.instanceId
|
25
|
+
? configManager.getConfigForSection(req.kapeta!.systemId, req.kapeta!.instanceId)
|
26
|
+
: configManager.getConfigForSystem(req.kapeta!.systemId);
|
27
|
+
|
28
|
+
res.send(config);
|
29
|
+
});
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Updates the full configuration for a given service.
|
33
|
+
*/
|
34
|
+
router.put('/instance', async (req: KapetaBodyRequest, res) => {
|
35
|
+
try {
|
36
|
+
let config = JSON.parse(req.stringBody ?? '{}');
|
37
|
+
if (!config) {
|
38
|
+
config = {};
|
39
|
+
}
|
40
|
+
|
41
|
+
if (req.kapeta!.instanceId) {
|
42
|
+
configManager.setConfigForSection(req.kapeta!.systemId, req.kapeta!.instanceId, config);
|
43
|
+
//Restart the instance if it is running after config change
|
44
|
+
await instanceManager.restartIfRunning(req.kapeta!.systemId, req.kapeta!.instanceId);
|
45
|
+
} else {
|
46
|
+
configManager.setConfigForSystem(req.kapeta!.systemId, config);
|
47
|
+
}
|
48
|
+
} catch (err: any) {
|
49
|
+
console.error('Failed to update instance config', err);
|
50
|
+
res.status(400).send({ error: err.message });
|
51
|
+
return;
|
52
|
+
}
|
53
|
+
|
54
|
+
res.status(202).send({ ok: true });
|
55
|
+
});
|
56
|
+
|
57
|
+
/**
|
58
|
+
* Returns the full configuration for a plan
|
59
|
+
*/
|
60
|
+
router.get('/system', (req: KapetaRequest, res) => {
|
61
|
+
const config = configManager.getConfigForSection(req.kapeta!.systemId, SYSTEM_ID);
|
62
|
+
|
63
|
+
res.send(config);
|
64
|
+
});
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Updates the full configuration for a plan
|
68
|
+
*/
|
69
|
+
router.put('/system', (req: KapetaBodyRequest, res) => {
|
70
|
+
let config = JSON.parse(req.stringBody ?? '{}');
|
71
|
+
if (!config) {
|
72
|
+
config = {};
|
73
|
+
}
|
74
|
+
configManager.setConfigForSection(req.kapeta!.systemId, SYSTEM_ID, config);
|
75
|
+
res.status(202).send({ ok: true });
|
76
|
+
});
|
77
|
+
|
78
|
+
/**
|
79
|
+
* Resolves and checks the identity of a block instance
|
80
|
+
*/
|
81
|
+
router.get('/identity', async (req: KapetaRequest, res) => {
|
82
|
+
const identity = {
|
83
|
+
systemId: req.kapeta!.systemId,
|
84
|
+
instanceId: req.kapeta!.instanceId,
|
85
|
+
};
|
86
|
+
|
87
|
+
if (!req.kapeta!.blockRef) {
|
88
|
+
res.status(400).send({ error: 'Missing required header "X-Kapeta-Block"' });
|
89
|
+
return;
|
90
|
+
}
|
91
|
+
|
92
|
+
try {
|
93
|
+
if (!identity.systemId || !identity.instanceId) {
|
94
|
+
const { systemId, instanceId } = await configManager.resolveIdentity(
|
95
|
+
req.kapeta!.blockRef,
|
96
|
+
identity.systemId
|
97
|
+
);
|
98
|
+
identity.systemId = systemId;
|
99
|
+
identity.instanceId = instanceId;
|
100
|
+
} else {
|
101
|
+
await configManager.verifyIdentity(req.kapeta!.blockRef, identity.systemId, identity.instanceId);
|
102
|
+
}
|
103
|
+
|
104
|
+
res.send(identity);
|
105
|
+
} catch (err: any) {
|
106
|
+
console.warn('Failed to resolve identity', err);
|
107
|
+
res.status(400).send({ error: err.message });
|
108
|
+
}
|
109
|
+
});
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Services call this to request a free port. If a service has
|
113
|
+
* already called the endpoint the same port is returned.
|
114
|
+
*/
|
115
|
+
router.get('/provides/:type', async (req: KapetaRequest, res) => {
|
116
|
+
//Get service port
|
117
|
+
res.send(
|
118
|
+
'' + (await serviceManager.ensureServicePort(req.kapeta!.systemId, req.kapeta!.instanceId, req.params.type))
|
119
|
+
);
|
120
|
+
});
|
121
|
+
|
122
|
+
/**
|
123
|
+
* Used by services to get info for consumed operator resource.
|
124
|
+
*
|
125
|
+
* If the operator resource is not already available this will cause it to start an instance and
|
126
|
+
* assign port numbers to it etc.
|
127
|
+
*/
|
128
|
+
router.get('/consumes/resource/:resourceType/:portType/:name', async (req: KapetaRequest, res) => {
|
129
|
+
const operatorInfo = await operatorManager.getConsumerResourceInfo(
|
130
|
+
req.kapeta!.systemId,
|
131
|
+
req.kapeta!.instanceId,
|
132
|
+
req.params.resourceType,
|
133
|
+
req.params.portType,
|
134
|
+
req.params.name,
|
135
|
+
req.kapeta!.environment
|
136
|
+
);
|
137
|
+
|
138
|
+
res.send(operatorInfo);
|
139
|
+
});
|
140
|
+
|
141
|
+
/**
|
142
|
+
* Used by services to get address for their clients.
|
143
|
+
*
|
144
|
+
* If the remote service is not already registered with a port - we do that here
|
145
|
+
* to handle clients for services that hasn't started yet.
|
146
|
+
*/
|
147
|
+
router.get('/consumes/:resourceName/:type', (req: KapetaRequest, res) => {
|
148
|
+
res.send(
|
149
|
+
serviceManager.getConsumerAddress(
|
150
|
+
req.kapeta!.systemId,
|
151
|
+
req.kapeta!.instanceId,
|
152
|
+
req.params.resourceName,
|
153
|
+
req.params.type,
|
154
|
+
req.kapeta!.environment
|
155
|
+
)
|
156
|
+
);
|
157
|
+
});
|
158
|
+
|
159
|
+
export default router;
|