@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.
Files changed (192) hide show
  1. package/.eslintrc.cjs +17 -0
  2. package/.github/workflows/main.yml +22 -22
  3. package/.prettierignore +4 -0
  4. package/.vscode/launch.json +2 -4
  5. package/CHANGELOG.md +7 -0
  6. package/definitions.d.ts +17 -35
  7. package/dist/cjs/index.d.ts +27 -0
  8. package/dist/cjs/index.js +126 -0
  9. package/dist/cjs/package.json +1 -0
  10. package/dist/cjs/src/assetManager.d.ts +31 -0
  11. package/dist/cjs/src/assetManager.js +153 -0
  12. package/dist/cjs/src/assets/routes.d.ts +3 -0
  13. package/dist/cjs/src/assets/routes.js +117 -0
  14. package/dist/cjs/src/clusterService.d.ts +40 -0
  15. package/dist/cjs/src/clusterService.js +114 -0
  16. package/dist/cjs/src/codeGeneratorManager.d.ts +8 -0
  17. package/dist/cjs/src/codeGeneratorManager.js +53 -0
  18. package/dist/cjs/src/config/routes.d.ts +3 -0
  19. package/dist/cjs/src/config/routes.js +126 -0
  20. package/dist/cjs/src/configManager.d.ts +36 -0
  21. package/dist/cjs/src/configManager.js +110 -0
  22. package/dist/cjs/src/containerManager.d.ts +89 -0
  23. package/dist/cjs/src/containerManager.js +365 -0
  24. package/dist/cjs/src/filesystem/routes.d.ts +3 -0
  25. package/dist/cjs/src/filesystem/routes.js +69 -0
  26. package/dist/cjs/src/filesystemManager.d.ts +15 -0
  27. package/dist/cjs/src/filesystemManager.js +87 -0
  28. package/dist/cjs/src/identities/routes.d.ts +3 -0
  29. package/dist/cjs/src/identities/routes.js +18 -0
  30. package/dist/cjs/src/instanceManager.d.ts +56 -0
  31. package/dist/cjs/src/instanceManager.js +424 -0
  32. package/dist/cjs/src/instances/routes.d.ts +3 -0
  33. package/dist/cjs/src/instances/routes.js +134 -0
  34. package/dist/cjs/src/middleware/cors.d.ts +2 -0
  35. package/dist/cjs/src/middleware/cors.js +10 -0
  36. package/dist/cjs/src/middleware/kapeta.d.ts +11 -0
  37. package/dist/cjs/src/middleware/kapeta.js +17 -0
  38. package/dist/cjs/src/middleware/stringBody.d.ts +5 -0
  39. package/dist/cjs/src/middleware/stringBody.js +14 -0
  40. package/dist/cjs/src/networkManager.d.ts +32 -0
  41. package/dist/cjs/src/networkManager.js +109 -0
  42. package/dist/cjs/src/operatorManager.d.ts +36 -0
  43. package/dist/cjs/src/operatorManager.js +165 -0
  44. package/dist/cjs/src/progressListener.d.ts +20 -0
  45. package/dist/cjs/src/progressListener.js +91 -0
  46. package/dist/cjs/src/providerManager.d.ts +9 -0
  47. package/dist/cjs/src/providerManager.js +51 -0
  48. package/dist/cjs/src/providers/routes.d.ts +3 -0
  49. package/dist/cjs/src/providers/routes.js +42 -0
  50. package/dist/cjs/src/proxy/routes.d.ts +3 -0
  51. package/dist/cjs/src/proxy/routes.js +111 -0
  52. package/dist/cjs/src/proxy/types/rest.d.ts +4 -0
  53. package/dist/cjs/src/proxy/types/rest.js +114 -0
  54. package/dist/cjs/src/proxy/types/web.d.ts +4 -0
  55. package/dist/cjs/src/proxy/types/web.js +53 -0
  56. package/dist/cjs/src/repositoryManager.d.ts +17 -0
  57. package/dist/cjs/src/repositoryManager.js +215 -0
  58. package/dist/cjs/src/serviceManager.d.ts +29 -0
  59. package/dist/cjs/src/serviceManager.js +99 -0
  60. package/dist/cjs/src/socketManager.d.ts +14 -0
  61. package/dist/cjs/src/socketManager.js +53 -0
  62. package/dist/cjs/src/storageService.d.ts +17 -0
  63. package/dist/cjs/src/storageService.js +74 -0
  64. package/dist/cjs/src/traffic/routes.d.ts +3 -0
  65. package/dist/cjs/src/traffic/routes.js +18 -0
  66. package/dist/cjs/src/types.d.ts +88 -0
  67. package/dist/cjs/src/types.js +2 -0
  68. package/dist/cjs/src/utils/BlockInstanceRunner.d.ts +29 -0
  69. package/dist/cjs/src/utils/BlockInstanceRunner.js +468 -0
  70. package/dist/cjs/src/utils/LogData.d.ts +19 -0
  71. package/dist/cjs/src/utils/LogData.js +43 -0
  72. package/dist/cjs/src/utils/pathTemplateParser.d.ts +26 -0
  73. package/dist/cjs/src/utils/pathTemplateParser.js +121 -0
  74. package/dist/cjs/src/utils/utils.d.ts +1 -0
  75. package/dist/cjs/src/utils/utils.js +18 -0
  76. package/dist/cjs/start.d.ts +1 -0
  77. package/dist/cjs/start.js +12 -0
  78. package/dist/esm/index.d.ts +27 -0
  79. package/dist/esm/index.js +121 -0
  80. package/dist/esm/package.json +1 -0
  81. package/dist/esm/src/assetManager.d.ts +31 -0
  82. package/{src → dist/esm/src}/assetManager.js +22 -60
  83. package/dist/esm/src/assets/routes.d.ts +3 -0
  84. package/{src → dist/esm/src}/assets/routes.js +21 -36
  85. package/dist/esm/src/clusterService.d.ts +40 -0
  86. package/{src → dist/esm/src}/clusterService.js +14 -37
  87. package/dist/esm/src/codeGeneratorManager.d.ts +8 -0
  88. package/{src → dist/esm/src}/codeGeneratorManager.js +15 -24
  89. package/dist/esm/src/config/routes.d.ts +3 -0
  90. package/{src → dist/esm/src}/config/routes.js +40 -89
  91. package/dist/esm/src/configManager.d.ts +36 -0
  92. package/{src → dist/esm/src}/configManager.js +11 -40
  93. package/dist/esm/src/containerManager.d.ts +89 -0
  94. package/{src → dist/esm/src}/containerManager.js +81 -182
  95. package/dist/esm/src/filesystem/routes.d.ts +3 -0
  96. package/dist/esm/src/filesystem/routes.js +64 -0
  97. package/dist/esm/src/filesystemManager.d.ts +15 -0
  98. package/{src → dist/esm/src}/filesystemManager.js +20 -28
  99. package/dist/esm/src/identities/routes.d.ts +3 -0
  100. package/dist/esm/src/identities/routes.js +13 -0
  101. package/dist/esm/src/instanceManager.d.ts +56 -0
  102. package/{src → dist/esm/src}/instanceManager.js +88 -179
  103. package/dist/esm/src/instances/routes.d.ts +3 -0
  104. package/{src → dist/esm/src}/instances/routes.js +31 -70
  105. package/dist/esm/src/middleware/cors.d.ts +2 -0
  106. package/{src → dist/esm/src}/middleware/cors.js +2 -3
  107. package/dist/esm/src/middleware/kapeta.d.ts +11 -0
  108. package/{src → dist/esm/src}/middleware/kapeta.js +3 -7
  109. package/dist/esm/src/middleware/stringBody.d.ts +5 -0
  110. package/{src → dist/esm/src}/middleware/stringBody.js +2 -3
  111. package/dist/esm/src/networkManager.d.ts +32 -0
  112. package/{src → dist/esm/src}/networkManager.js +16 -33
  113. package/dist/esm/src/operatorManager.d.ts +36 -0
  114. package/{src → dist/esm/src}/operatorManager.js +35 -91
  115. package/dist/esm/src/progressListener.d.ts +20 -0
  116. package/dist/esm/src/progressListener.js +88 -0
  117. package/dist/esm/src/providerManager.d.ts +9 -0
  118. package/dist/esm/src/providerManager.js +45 -0
  119. package/dist/esm/src/providers/routes.d.ts +3 -0
  120. package/{src → dist/esm/src}/providers/routes.js +10 -16
  121. package/dist/esm/src/proxy/routes.d.ts +3 -0
  122. package/dist/esm/src/proxy/routes.js +106 -0
  123. package/dist/esm/src/proxy/types/rest.d.ts +4 -0
  124. package/dist/esm/src/proxy/types/rest.js +107 -0
  125. package/dist/esm/src/proxy/types/web.d.ts +4 -0
  126. package/{src → dist/esm/src}/proxy/types/web.js +13 -35
  127. package/dist/esm/src/repositoryManager.d.ts +17 -0
  128. package/dist/esm/src/repositoryManager.js +209 -0
  129. package/dist/esm/src/serviceManager.d.ts +29 -0
  130. package/{src → dist/esm/src}/serviceManager.js +12 -42
  131. package/dist/esm/src/socketManager.d.ts +14 -0
  132. package/{src → dist/esm/src}/socketManager.js +19 -23
  133. package/dist/esm/src/storageService.d.ts +17 -0
  134. package/{src → dist/esm/src}/storageService.js +8 -27
  135. package/dist/esm/src/traffic/routes.d.ts +3 -0
  136. package/{src → dist/esm/src}/traffic/routes.js +4 -9
  137. package/dist/esm/src/types.d.ts +88 -0
  138. package/dist/esm/src/types.js +1 -0
  139. package/dist/esm/src/utils/BlockInstanceRunner.d.ts +29 -0
  140. package/{src → dist/esm/src}/utils/BlockInstanceRunner.js +137 -256
  141. package/dist/esm/src/utils/LogData.d.ts +19 -0
  142. package/{src → dist/esm/src}/utils/LogData.js +11 -22
  143. package/dist/esm/src/utils/pathTemplateParser.d.ts +26 -0
  144. package/{src → dist/esm/src}/utils/pathTemplateParser.js +21 -40
  145. package/dist/esm/src/utils/utils.d.ts +1 -0
  146. package/dist/esm/src/utils/utils.js +11 -0
  147. package/dist/esm/start.d.ts +1 -0
  148. package/dist/esm/start.js +7 -0
  149. package/index.ts +147 -0
  150. package/package.json +106 -74
  151. package/src/assetManager.ts +191 -0
  152. package/src/assets/routes.ts +132 -0
  153. package/src/clusterService.ts +134 -0
  154. package/src/codeGeneratorManager.ts +57 -0
  155. package/src/config/routes.ts +159 -0
  156. package/src/configManager.ts +148 -0
  157. package/src/containerManager.ts +466 -0
  158. package/src/filesystem/routes.ts +74 -0
  159. package/src/filesystemManager.ts +93 -0
  160. package/src/identities/routes.ts +20 -0
  161. package/src/instanceManager.ts +503 -0
  162. package/src/instances/routes.ts +164 -0
  163. package/src/middleware/cors.ts +9 -0
  164. package/src/middleware/kapeta.ts +27 -0
  165. package/src/middleware/stringBody.ts +16 -0
  166. package/src/networkManager.ts +137 -0
  167. package/src/operatorManager.ts +221 -0
  168. package/src/progressListener.ts +102 -0
  169. package/src/{providerManager.js → providerManager.ts} +15 -31
  170. package/src/providers/routes.ts +46 -0
  171. package/src/proxy/routes.ts +148 -0
  172. package/src/proxy/types/{rest.js → rest.ts} +30 -30
  173. package/src/proxy/types/web.ts +60 -0
  174. package/src/{repositoryManager.js → repositoryManager.ts} +45 -73
  175. package/src/serviceManager.ts +120 -0
  176. package/src/socketManager.ts +57 -0
  177. package/src/storageService.ts +88 -0
  178. package/src/traffic/routes.ts +18 -0
  179. package/src/types.ts +97 -0
  180. package/src/utils/BlockInstanceRunner.ts +555 -0
  181. package/src/utils/LogData.ts +47 -0
  182. package/src/utils/pathTemplateParser.ts +138 -0
  183. package/src/utils/utils.ts +12 -0
  184. package/start.ts +8 -0
  185. package/tsconfig.json +13 -0
  186. package/index.js +0 -127
  187. package/src/filesystem/routes.js +0 -74
  188. package/src/identities/routes.js +0 -19
  189. package/src/progressListener.js +0 -82
  190. package/src/proxy/routes.js +0 -126
  191. package/src/utils/utils.js +0 -13
  192. 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;