@kapeta/local-cluster-service 0.31.0 → 0.32.1

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 (51) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/index.js +2 -0
  3. package/dist/cjs/src/RepositoryWatcher.js +30 -6
  4. package/dist/cjs/src/ai/aiClient.d.ts +20 -0
  5. package/dist/cjs/src/ai/aiClient.js +74 -0
  6. package/dist/cjs/src/ai/routes.d.ts +7 -0
  7. package/dist/cjs/src/ai/routes.js +37 -0
  8. package/dist/cjs/src/ai/transform.d.ts +11 -0
  9. package/dist/cjs/src/ai/transform.js +239 -0
  10. package/dist/cjs/src/ai/types.d.ts +40 -0
  11. package/dist/cjs/src/ai/types.js +2 -0
  12. package/dist/cjs/src/assetManager.js +5 -2
  13. package/dist/cjs/src/codeGeneratorManager.js +4 -3
  14. package/dist/cjs/src/containerManager.js +7 -1
  15. package/dist/cjs/src/definitionsManager.js +2 -2
  16. package/dist/cjs/src/instanceManager.js +2 -2
  17. package/dist/cjs/src/repositoryManager.js +1 -1
  18. package/dist/cjs/src/utils/utils.d.ts +1 -0
  19. package/dist/cjs/src/utils/utils.js +7 -1
  20. package/dist/esm/index.js +2 -0
  21. package/dist/esm/src/RepositoryWatcher.js +30 -6
  22. package/dist/esm/src/ai/aiClient.d.ts +20 -0
  23. package/dist/esm/src/ai/aiClient.js +74 -0
  24. package/dist/esm/src/ai/routes.d.ts +7 -0
  25. package/dist/esm/src/ai/routes.js +37 -0
  26. package/dist/esm/src/ai/transform.d.ts +11 -0
  27. package/dist/esm/src/ai/transform.js +239 -0
  28. package/dist/esm/src/ai/types.d.ts +40 -0
  29. package/dist/esm/src/ai/types.js +2 -0
  30. package/dist/esm/src/assetManager.js +5 -2
  31. package/dist/esm/src/codeGeneratorManager.js +4 -3
  32. package/dist/esm/src/containerManager.js +7 -1
  33. package/dist/esm/src/definitionsManager.js +2 -2
  34. package/dist/esm/src/instanceManager.js +2 -2
  35. package/dist/esm/src/repositoryManager.js +1 -1
  36. package/dist/esm/src/utils/utils.d.ts +1 -0
  37. package/dist/esm/src/utils/utils.js +7 -1
  38. package/index.ts +2 -0
  39. package/package.json +1 -1
  40. package/src/RepositoryWatcher.ts +34 -6
  41. package/src/ai/aiClient.ts +93 -0
  42. package/src/ai/routes.ts +39 -0
  43. package/src/ai/transform.ts +275 -0
  44. package/src/ai/types.ts +45 -0
  45. package/src/assetManager.ts +5 -1
  46. package/src/codeGeneratorManager.ts +6 -4
  47. package/src/containerManager.ts +7 -1
  48. package/src/definitionsManager.ts +2 -2
  49. package/src/instanceManager.ts +2 -2
  50. package/src/repositoryManager.ts +1 -1
  51. package/src/utils/utils.ts +6 -0
@@ -7,15 +7,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
8
  };
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.getResolvedConfiguration = exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.getBlockInstanceContainerName = void 0;
10
+ exports.getResolvedConfiguration = exports.getBindHost = exports.isLinux = exports.isMac = exports.isWindows = exports.readYML = exports.getRemoteUrl = exports.getBlockInstanceContainerName = void 0;
11
11
  const node_fs_1 = __importDefault(require("node:fs"));
12
12
  const yaml_1 = __importDefault(require("yaml"));
13
13
  const md5_1 = __importDefault(require("md5"));
14
14
  const lodash_1 = __importDefault(require("lodash"));
15
+ const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
15
16
  function getBlockInstanceContainerName(systemId, instanceId) {
16
17
  return `kapeta-block-instance-${(0, md5_1.default)(systemId + instanceId)}`;
17
18
  }
18
19
  exports.getBlockInstanceContainerName = getBlockInstanceContainerName;
20
+ function getRemoteUrl(id, defautValue) {
21
+ const remoteConfig = local_cluster_config_1.default.getClusterConfig().remote;
22
+ return remoteConfig?.[id] ?? defautValue;
23
+ }
24
+ exports.getRemoteUrl = getRemoteUrl;
19
25
  function readYML(path) {
20
26
  const rawYaml = node_fs_1.default.readFileSync(path);
21
27
  try {
package/index.ts CHANGED
@@ -23,6 +23,7 @@ import ProviderRoutes from './src/providers/routes';
23
23
  import AttachmentRoutes from './src/attachments/routes';
24
24
  import TaskRoutes from './src/tasks/routes';
25
25
  import APIRoutes from './src/api';
26
+ import AIRoutes from './src/ai/routes';
26
27
  import { getBindHost } from './src/utils/utils';
27
28
  import request from 'request';
28
29
  import { repositoryManager } from './src/repositoryManager';
@@ -72,6 +73,7 @@ function createServer() {
72
73
  app.use('/attachments', AttachmentRoutes);
73
74
  app.use('/tasks', TaskRoutes);
74
75
  app.use('/api', APIRoutes);
76
+ app.use('/ai', AIRoutes);
75
77
 
76
78
  app.get('/status', async (req, res) => {
77
79
  res.send({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kapeta/local-cluster-service",
3
- "version": "0.31.0",
3
+ "version": "0.32.1",
4
4
  "description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -15,6 +15,7 @@ import { SourceOfChange, WatchEventName } from './types';
15
15
  import { cacheManager } from './cacheManager';
16
16
  import { EventEmitter } from 'node:events';
17
17
  import { assetManager } from './assetManager';
18
+ import { definitionsManager } from './definitionsManager';
18
19
 
19
20
  interface AssetIdentity {
20
21
  handle: string;
@@ -22,6 +23,27 @@ interface AssetIdentity {
22
23
  version: string;
23
24
  }
24
25
  const KAPETA_YML_RX = /^kapeta.ya?ml$/;
26
+
27
+ let definitions: DefinitionInfo[] | undefined;
28
+ let definitionTimeout: NodeJS.Timeout | undefined;
29
+
30
+ function getDefinitionsDebounced() {
31
+ if (definitionTimeout) {
32
+ clearTimeout(definitionTimeout);
33
+ definitionTimeout = undefined;
34
+ }
35
+ if (!definitions) {
36
+ definitions = ClusterConfiguration.getDefinitions();
37
+ } else {
38
+ definitionTimeout = setTimeout(() => {
39
+ definitionTimeout = undefined;
40
+ definitions = undefined;
41
+ }, 500);
42
+ }
43
+
44
+ return definitions;
45
+ }
46
+
25
47
  export class RepositoryWatcher extends EventEmitter {
26
48
  private watcher?: FSWatcher;
27
49
  private disabled: boolean = false;
@@ -208,7 +230,8 @@ export class RepositoryWatcher extends EventEmitter {
208
230
 
209
231
  private async checkForChange(assetIdentity: AssetIdentity, sourceOfChange: SourceOfChange) {
210
232
  const ymlPath = Path.join(this.getRepositoryPath(assetIdentity), 'kapeta.yml');
211
- const newDefinitions = ClusterConfiguration.getDefinitions();
233
+ const newDefinitions = getDefinitionsDebounced();
234
+
212
235
  const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
213
236
  let currentDefinition = this.allDefinitions.find((d) => d.ymlPath === ymlPath);
214
237
  const ymlExists = await this.exists(ymlPath);
@@ -290,11 +313,16 @@ export class RepositoryWatcher extends EventEmitter {
290
313
  } catch (e) {}
291
314
 
292
315
  if (symbolicLink) {
293
- const realPath = Path.join(await FS.realpath(path), 'kapeta.yml');
294
- if (await this.exists(realPath)) {
295
- //console.log('Watching symlink target %s => %s', path, realPath);
296
- this.watcher?.add(realPath);
297
- this.symbolicLinks[path] = realPath;
316
+ try {
317
+ const realPath = Path.join(await FS.realpath(path), 'kapeta.yml');
318
+ if (await this.exists(realPath)) {
319
+ //console.log('Watching symlink target %s => %s', path, realPath);
320
+ this.watcher?.add(realPath);
321
+ this.symbolicLinks[path] = realPath;
322
+ }
323
+ } catch (e) {
324
+ // Remove the symlink - it's broken
325
+ await FS.remove(path);
298
326
  }
299
327
  }
300
328
  } catch (e) {
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ import request from 'request';
6
+ import { PlanContext, transformToPlan } from './transform';
7
+ import { Application } from './types';
8
+ import { KapetaAPI } from '@kapeta/nodejs-api-client';
9
+ import ClusterConfiguration from '@kapeta/local-cluster-config';
10
+ import { getRemoteUrl } from '../utils/utils';
11
+
12
+ export type PromptResult = {
13
+ explanation: string;
14
+ response: string;
15
+ context?: PlanContext;
16
+ };
17
+
18
+ export interface AIMessage {
19
+ content: string;
20
+ role: 'user' | 'assistant';
21
+ }
22
+
23
+ export interface AIRequest {
24
+ messages: AIMessage[];
25
+ }
26
+
27
+ class AIClient {
28
+ private readonly _baseUrl: string;
29
+
30
+ constructor() {
31
+ this._baseUrl = getRemoteUrl('ai-service', 'https://ai.kapeta.com');
32
+ }
33
+
34
+ public async sendPrompt(handle: string, body: AIRequest): Promise<PromptResult> {
35
+ const url = `${this._baseUrl}/v1/plan?type=chat`;
36
+
37
+ const headers: { [k: string]: string } = {};
38
+ const api = new KapetaAPI();
39
+ if (api.hasToken()) {
40
+ headers['Authorization'] = `Bearer ${await api.getAccessToken()}`;
41
+ }
42
+
43
+ const options = {
44
+ url,
45
+ method: 'POST',
46
+ json: true,
47
+ body,
48
+ headers,
49
+ };
50
+
51
+ return new Promise((resolve, reject) => {
52
+ request(options, async (error, response, application: Application) => {
53
+ if (error) {
54
+ console.error(error);
55
+ reject(error);
56
+ }
57
+
58
+ if (response.statusCode !== 200) {
59
+ console.log('Prompt failed', response.statusCode, response.body);
60
+ reject(new Error(`Invalid response code: ${response.statusCode}`));
61
+ return;
62
+ }
63
+
64
+ try {
65
+ if (application?.name) {
66
+ const planContext = await transformToPlan(handle, application);
67
+ resolve({
68
+ explanation: application.explanation,
69
+ response: application.response ?? application.explanation ?? 'Plan was generated',
70
+ context: planContext,
71
+ });
72
+ } else {
73
+ resolve({
74
+ explanation: application.explanation,
75
+ response:
76
+ application.response ??
77
+ application.explanation ??
78
+ 'I did not understand your request. Please rephrase.',
79
+ });
80
+ }
81
+ } catch (err: any) {
82
+ console.error(err);
83
+ resolve({
84
+ explanation: '',
85
+ response: 'I did not understand your request. Please rephrase.',
86
+ });
87
+ }
88
+ });
89
+ });
90
+ }
91
+ }
92
+
93
+ export const aiClient = new AIClient();
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+
6
+ import Router from 'express-promise-router';
7
+ import { Response } from 'express';
8
+
9
+ import { corsHandler } from '../middleware/cors';
10
+
11
+ import { stringBody, StringBodyRequest } from '../middleware/stringBody';
12
+ import { aiClient, AIRequest } from './aiClient';
13
+ import { KapetaBodyRequest } from '../types';
14
+ import YAML from 'yaml';
15
+
16
+ const router = Router();
17
+
18
+ router.use('/', corsHandler);
19
+ router.use('/', stringBody);
20
+
21
+ router.post('/prompt/:handle', async (req: KapetaBodyRequest, res: Response) => {
22
+ const handle = req.params.handle;
23
+ try {
24
+ const aiRequest: AIRequest = JSON.parse(req.stringBody ?? '{}');
25
+ const result = await aiClient.sendPrompt(handle, aiRequest);
26
+ if (req.accepts('application/yaml')) {
27
+ res.set('Content-Type', 'application/yaml');
28
+ res.send(YAML.stringify(result));
29
+ } else {
30
+ res.json(result);
31
+ }
32
+ } catch (err: any) {
33
+ console.error('Failed to send prompt', err);
34
+ res.status(400).send({ error: err.message });
35
+ return;
36
+ }
37
+ });
38
+
39
+ export default router;
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ import { BlockDefinition, Plan } from '@kapeta/schemas';
6
+ import { Application } from './types';
7
+ import { definitionsManager } from '../definitionsManager';
8
+ import { normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
9
+ import uuid from 'node-uuid';
10
+
11
+ export type PlanContext = {
12
+ plan: Plan;
13
+ blocks: BlockDefinition[];
14
+ };
15
+
16
+ async function getFreeName(name: string) {
17
+ let currentName = name;
18
+ let iteration = 1;
19
+ do {
20
+ const found = await definitionsManager.getLatestDefinition(currentName);
21
+ if (!found) {
22
+ return currentName;
23
+ }
24
+ currentName = name + '_' + iteration++;
25
+ } while (true);
26
+ }
27
+
28
+ export const transformToPlan = async (handle: string, application: Application): Promise<PlanContext> => {
29
+ const blockTypeService = await definitionsManager.getLatestDefinition('kapeta/block-type-service');
30
+ const blockTypeFrontend = await definitionsManager.getLatestDefinition('kapeta/block-type-frontend');
31
+ const mongoDbResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-mongodb');
32
+ const postgresResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-postgresql');
33
+ const webPageResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-web-page');
34
+ const webFragmentResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-web-fragment');
35
+ const restApiResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-rest-api');
36
+ const restClientResource = await definitionsManager.getLatestDefinition('kapeta/resource-type-rest-client');
37
+ const javaLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-java-spring-boot');
38
+ const nodejsLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-nodejs');
39
+ const reactLanguage = await definitionsManager.getLatestDefinition('kapeta/language-target-react-ts');
40
+
41
+ if (
42
+ !blockTypeService ||
43
+ !blockTypeFrontend ||
44
+ !mongoDbResource ||
45
+ !postgresResource ||
46
+ !javaLanguage ||
47
+ !nodejsLanguage ||
48
+ !reactLanguage ||
49
+ !webPageResource ||
50
+ !restApiResource ||
51
+ !restClientResource ||
52
+ !webFragmentResource
53
+ ) {
54
+ throw new Error('Missing definitions');
55
+ }
56
+
57
+ const plan: Plan = {
58
+ kind: 'core/plan',
59
+ metadata: {
60
+ name: await getFreeName(`${handle}/${application.name}`),
61
+ title: application.title,
62
+ description: application.description,
63
+ visibility: 'private',
64
+ },
65
+ spec: {
66
+ blocks: [],
67
+ connections: [],
68
+ },
69
+ };
70
+
71
+ const blocks: BlockDefinition[] = [];
72
+
73
+ const addToPlan = (ref: string, name: string) => {
74
+ const top = 100 + Math.floor(plan.spec.blocks.length / 3) * 300;
75
+ const left = 200 + (plan.spec.blocks.length % 3) * 450;
76
+
77
+ plan.spec.blocks.push({
78
+ block: {
79
+ ref,
80
+ },
81
+ name,
82
+ id: uuid.v4(),
83
+ dimensions: {
84
+ top,
85
+ left,
86
+ width: -1,
87
+ height: -1,
88
+ },
89
+ });
90
+ };
91
+
92
+ const nameMapper = new Map<string, string>();
93
+
94
+ for (const backend of application.backends) {
95
+ const blockName = `${handle}/${backend.name}`;
96
+ const blockRealName = await getFreeName(blockName);
97
+ nameMapper.set(blockName, blockRealName);
98
+
99
+ const language = backend.targetLanguage === 'java' ? javaLanguage : nodejsLanguage;
100
+ const databaseInfo = backend.databases?.[0];
101
+ const database = databaseInfo?.type === 'mongodb' ? mongoDbResource : postgresResource;
102
+
103
+ const blockRef = normalizeKapetaUri(blockRealName + ':local');
104
+ let targetOptions = {};
105
+ if (backend.targetLanguage === 'java') {
106
+ targetOptions = {
107
+ basePackage: `${handle}.${application.name}`.toLowerCase().replace(/-/g, '_'),
108
+ groupId: `${handle}.${application.name}`.toLowerCase().replace(/-/g, '_'),
109
+ artifactId: backend.name.toLowerCase().replace(/-/g, '_'),
110
+ };
111
+ }
112
+ blocks.push({
113
+ kind: normalizeKapetaUri(`${blockTypeService.definition.metadata.name}:${blockTypeService.version}`),
114
+ metadata: {
115
+ name: blockRealName,
116
+ title: backend.title,
117
+ description: backend.description,
118
+ visibility: 'private',
119
+ },
120
+ spec: {
121
+ target: {
122
+ kind: normalizeKapetaUri(`${language.definition.metadata.name}:${language.version}`),
123
+ options: targetOptions,
124
+ },
125
+ consumers: [
126
+ {
127
+ kind: normalizeKapetaUri(`${database.definition.metadata.name}:${database.version}`),
128
+ metadata: {
129
+ name: (databaseInfo?.name ?? 'main').replace(/-/g, ''),
130
+ },
131
+ spec: {
132
+ port: {
133
+ type: database.definition.spec.ports[0].type,
134
+ },
135
+ },
136
+ },
137
+ ],
138
+ providers: [
139
+ {
140
+ kind: normalizeKapetaUri(
141
+ `${restApiResource.definition.metadata.name}:${restApiResource.version}`
142
+ ),
143
+ metadata: {
144
+ name: 'main',
145
+ },
146
+ spec: {
147
+ port: {
148
+ type: restApiResource.definition.spec.ports[0].type,
149
+ },
150
+ },
151
+ },
152
+ ],
153
+ },
154
+ });
155
+
156
+ addToPlan(blockRef, backend.name);
157
+ }
158
+
159
+ for (const frontend of application.frontends) {
160
+ const blockName = `${handle}/${frontend.name}`;
161
+ const blockRealName = await getFreeName(blockName);
162
+ nameMapper.set(blockName, blockRealName);
163
+
164
+ const language = reactLanguage;
165
+
166
+ const blockRef = normalizeKapetaUri(blockRealName + ':local');
167
+ blocks.push({
168
+ kind: normalizeKapetaUri(`${blockTypeFrontend.definition.metadata.name}:${blockTypeFrontend.version}`),
169
+ metadata: {
170
+ name: blockRealName,
171
+ title: frontend.title,
172
+ description: frontend.description,
173
+ visibility: 'private',
174
+ },
175
+ spec: {
176
+ target: {
177
+ kind: normalizeKapetaUri(`${language.definition.metadata.name}:${language.version}`),
178
+ options: {},
179
+ },
180
+ consumers: [],
181
+ providers: [
182
+ {
183
+ kind: normalizeKapetaUri(
184
+ `${webPageResource.definition.metadata.name}:${webPageResource.version}`
185
+ ),
186
+ metadata: {
187
+ name: 'main',
188
+ },
189
+ spec: {
190
+ port: {
191
+ type: webPageResource.definition.spec.ports[0].type,
192
+ },
193
+ },
194
+ },
195
+ ],
196
+ },
197
+ });
198
+
199
+ addToPlan(blockRef, frontend.name);
200
+ }
201
+
202
+ application.connections?.forEach((connection) => {
203
+ const fullProviderName = nameMapper.get(`${handle}/${connection.provider.name}`) as string;
204
+ const fullConsumerName = nameMapper.get(`${handle}/${connection.consumer.name}`) as string;
205
+ const consumerResourceName = connection.provider.name;
206
+ const providerRef = normalizeKapetaUri(`${fullProviderName}:local`);
207
+ const consumerRef = normalizeKapetaUri(`${fullConsumerName}:local`);
208
+
209
+ const instanceProvider = plan.spec.blocks.find((b) => b.block.ref === providerRef)!;
210
+ const instanceConsumer = plan.spec.blocks.find((b) => b.block.ref === consumerRef)!;
211
+ const consumerBlock = blocks.find((block) => block.metadata.name === fullConsumerName);
212
+ const providerBlock = blocks.find((block) => block.metadata.name === fullProviderName);
213
+ if (!consumerBlock) {
214
+ throw new Error('Missing consumer block: ' + fullConsumerName);
215
+ }
216
+
217
+ if (!providerBlock) {
218
+ throw new Error('Missing provider block: ' + fullProviderName);
219
+ }
220
+
221
+ const portType = parseKapetaUri(providerBlock.kind).fullName === 'kapeta/block-type-service' ? 'rest' : 'web';
222
+
223
+ if (portType === 'rest') {
224
+ consumerBlock.spec.consumers!.push({
225
+ kind: normalizeKapetaUri(
226
+ `${restClientResource.definition.metadata.name}:${restClientResource.version}`
227
+ ),
228
+ metadata: {
229
+ name: consumerResourceName,
230
+ },
231
+ spec: {
232
+ port: {
233
+ type: 'rest',
234
+ },
235
+ },
236
+ });
237
+ } else {
238
+ consumerBlock.spec.consumers!.push({
239
+ kind: normalizeKapetaUri(
240
+ `${webFragmentResource.definition.metadata.name}:${webFragmentResource.version}`
241
+ ),
242
+ metadata: {
243
+ name: consumerResourceName,
244
+ },
245
+ spec: {
246
+ port: {
247
+ type: 'web',
248
+ },
249
+ },
250
+ });
251
+ }
252
+
253
+ plan.spec.connections.push({
254
+ provider: {
255
+ blockId: instanceProvider.id,
256
+ resourceName: 'main',
257
+ port: {
258
+ type: portType,
259
+ },
260
+ },
261
+ consumer: {
262
+ blockId: instanceConsumer.id,
263
+ resourceName: consumerResourceName,
264
+ port: {
265
+ type: portType,
266
+ },
267
+ },
268
+ });
269
+ });
270
+
271
+ return {
272
+ plan,
273
+ blocks,
274
+ };
275
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Copyright 2023 Kapeta Inc.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ */
5
+ export interface Database {
6
+ name: string;
7
+ type: 'mongodb' | 'postgres';
8
+ }
9
+
10
+ export interface BackendService {
11
+ name: string;
12
+ title: string;
13
+ description: string;
14
+ targetLanguage: 'java' | 'node';
15
+ databases: Database[] | null;
16
+ }
17
+
18
+ export interface FrontendService {
19
+ name: string;
20
+ title: string;
21
+ description: string;
22
+ targetLanguage: 'react';
23
+ }
24
+
25
+ export interface Endpoint {
26
+ type: 'backend' | 'frontend';
27
+ name: string;
28
+ }
29
+
30
+ export interface Connection {
31
+ provider: Endpoint;
32
+ consumer: Endpoint;
33
+ }
34
+
35
+ export interface Application {
36
+ kind: 'core/plan';
37
+ name: string;
38
+ title: string;
39
+ description: string;
40
+ backends: BackendService[];
41
+ frontends: FrontendService[];
42
+ connections: Connection[];
43
+ explanation: string;
44
+ response: string;
45
+ }
@@ -36,6 +36,10 @@ export interface EnrichedAsset {
36
36
  ymlPath: string;
37
37
  }
38
38
 
39
+ function filterExists(asset: DefinitionInfo): boolean {
40
+ return FS.existsSync(asset.path);
41
+ }
42
+
39
43
  function enrichAsset(asset: DefinitionInfo): EnrichedAsset {
40
44
  return {
41
45
  ref: `kapeta://${asset.definition.metadata.name}:${asset.version}`,
@@ -101,7 +105,7 @@ class AssetManager {
101
105
 
102
106
  const assets = await definitionsManager.getDefinitions(assetKinds);
103
107
 
104
- return assets.map(enrichAsset);
108
+ return assets.filter(filterExists).map(enrichAsset);
105
109
  }
106
110
 
107
111
  async getPlans(): Promise<EnrichedAsset[]> {
@@ -9,7 +9,7 @@ import { BlockDefinition } from '@kapeta/schemas';
9
9
  import { definitionsManager } from './definitionsManager';
10
10
  import { Definition } from '@kapeta/local-cluster-config';
11
11
  import { assetManager } from './assetManager';
12
- import { normalizeKapetaUri } from '@kapeta/nodejs-utils';
12
+ import { normalizeKapetaUri, parseKapetaUri } from '@kapeta/nodejs-utils';
13
13
  import { repositoryManager } from './repositoryManager';
14
14
 
15
15
  const TARGET_KIND = 'core/language-target';
@@ -61,16 +61,18 @@ class CodeGeneratorManager {
61
61
  }
62
62
 
63
63
  async canGenerateCode(yamlContent: Definition): Promise<boolean> {
64
- if (!yamlContent.spec.target?.kind) {
64
+ if (!yamlContent.spec?.target?.kind || !yamlContent.kind) {
65
65
  //Not all block types have targets
66
66
  return false;
67
67
  }
68
68
 
69
+ const kindUri = parseKapetaUri(yamlContent.kind);
70
+
69
71
  const blockTypes = await definitionsManager.getDefinitions(BLOCK_TYPE_KIND);
70
72
  const blockTypeKinds = blockTypes.map(
71
- (blockType) => blockType.definition.metadata.name.toLowerCase() + ':' + blockType.version
73
+ (blockType) => parseKapetaUri(blockType.definition.metadata.name).fullName
72
74
  );
73
- return !!(yamlContent && yamlContent.kind && blockTypeKinds.indexOf(yamlContent.kind.toLowerCase()) > -1);
75
+ return blockTypeKinds.includes(kindUri.fullName);
74
76
  }
75
77
 
76
78
  async generate(yamlFile: string, yamlContent: Definition) {
@@ -166,11 +166,17 @@ class ContainerManager {
166
166
  ];
167
167
  for (const opts of connectOptions) {
168
168
  try {
169
+ const testClient = new Docker({
170
+ ...opts,
171
+ timeout: 1000, // 1 secs should be enough for a ping
172
+ });
173
+ await testClient.ping();
174
+ // If we get here - we have a working connection
175
+ // Now create a client with a longer timeout for all other operations
169
176
  const client = new Docker({
170
177
  ...opts,
171
178
  timeout: 15 * 60 * 1000, //15 minutes should be enough for any operation
172
179
  });
173
- await client.ping();
174
180
  this._docker = client;
175
181
  const versionInfo: any = await client.version();
176
182
  this._version = versionInfo.Server?.Version ?? versionInfo.Version;
@@ -164,9 +164,9 @@ class DefinitionsManager {
164
164
  const definitions = await this.getDefinitions();
165
165
  return definitions.find((d) => {
166
166
  if (!uri.version) {
167
- return d.definition.metadata.name === uri.fullName;
167
+ return d.definition.metadata.name.toLowerCase() === uri.fullName.toLowerCase();
168
168
  }
169
- return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
169
+ return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).equals(uri);
170
170
  });
171
171
  }
172
172
 
@@ -27,7 +27,7 @@ import { definitionsManager } from './definitionsManager';
27
27
  import { Task, taskManager } from './taskManager';
28
28
 
29
29
  const CHECK_INTERVAL = 5000;
30
- const DEFAULT_HEALTH_PORT_TYPE = 'rest';
30
+ const DEFAULT_HEALTH_PORT_TYPE = 'http';
31
31
 
32
32
  const MIN_TIME_RUNNING = 30000; //If something didnt run for more than 30 secs - it failed
33
33
 
@@ -264,7 +264,7 @@ export class InstanceManager {
264
264
 
265
265
  private getHealthUrl(info: Omit<InstanceInfo, 'systemId' | 'instanceId'>, address: string) {
266
266
  let healthUrl = null;
267
- let health = info.health;
267
+ let health = info.health ?? '/.kapeta/health';
268
268
  if (health) {
269
269
  if (health.startsWith('/')) {
270
270
  health = health.substring(1);