@kumori/aurora-backend-handler 1.0.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.
@@ -0,0 +1,899 @@
1
+
2
+ import { Account, Container, Environment, Instance, Resource, Service, Usage } from "@hestekumori/aurora-interfaces";
3
+ import { convertToGigabytes, getTimestamp } from "../utils/utils";
4
+
5
+ interface Role {
6
+ name: string;
7
+ instances: Instance[];
8
+ logo?: string;
9
+ category?: string;
10
+ version?: string;
11
+ description?: string;
12
+ resource?: any[];
13
+ parameters?: { [key: string]: string }[];
14
+ registry?: string;
15
+ imageTag?: string;
16
+ entrypoint?: string;
17
+ cmd?: string;
18
+ scalling?: {
19
+ cpu: { up: string; down: string };
20
+ memory: { up: string; down: string };
21
+ instances: { max: number; min: number };
22
+ histeresys: string;
23
+ };
24
+ hsize?: number;
25
+ }
26
+
27
+ interface Revision {
28
+ service: string;
29
+ revision: string;
30
+ usage: Usage;
31
+ status: {
32
+ code: string;
33
+ message: string;
34
+ timestamp: string;
35
+ args: string[];
36
+ };
37
+ errorCode?: string;
38
+ errorMsg?: string;
39
+ createdAt?: string;
40
+ }
41
+
42
+ interface HandleRevisionEventParams {
43
+ entityId: string;
44
+ eventData: any;
45
+ parentParts: { [entity: string]: string };
46
+ servicesMap: Map<string, Service>;
47
+ revisionsMap: Map<string, Revision>;
48
+ roleMap: Map<string, Role[]>;
49
+ environmentsMap: Map<string, Environment>;
50
+ accountsMap: Map<string, Account>;
51
+ }
52
+
53
+ interface HandleRevisionEventResult {
54
+ revision: Revision;
55
+ revisionKey: string;
56
+ serviceId: string;
57
+ roles: Role[];
58
+ updatedService: Service | null;
59
+ updatedEnvironment: Environment | null;
60
+ updatedAccount: Account | null;
61
+ pendingRevisionError: { service: string; revision: Revision } | null;
62
+ shouldFetchChannels: boolean;
63
+ channelsFetchInfo: { serviceId: string; tenantId: string } | null;
64
+ serviceDeployedEvent: Service | null;
65
+ serviceDeploymentErrorEvent: Service | null;
66
+ }
67
+
68
+ const MAX_HISTORY = 100;
69
+
70
+ /**
71
+ * Process role and instance data from revision event
72
+ */
73
+ const processRolesAndInstances = (
74
+ eventData: any,
75
+ serviceId: string,
76
+ roleMap: Map<string, Role[]>
77
+ ): { roles: Role[]; instances: Instance[]; usedCpu: number; usedMemory: number } => {
78
+ const roles: Role[] = [];
79
+ const instances: Instance[] = [];
80
+ let usedCpu = 0;
81
+ let usedMemory = 0;
82
+
83
+ const roleEntries = eventData.status.runtime
84
+ ? Object.entries(eventData.status.runtime.roles)
85
+ : [];
86
+
87
+ for (const [roleName, roleData] of roleEntries) {
88
+ const instanceEntries = Object.entries(
89
+ (roleData as { instances: Record<string, any> }).instances
90
+ );
91
+ const roleInstances: Instance[] = [];
92
+ const existingRoles = roleMap.get(serviceId) || [];
93
+ const existingRole = existingRoles.find((r) => r.name === roleName);
94
+
95
+ for (const [instanceId, instanceData] of instanceEntries) {
96
+ const containers: Container[] = [];
97
+
98
+ const containerEntries = Object.entries(instanceData.containers || {});
99
+ for (const [containerName, containerData] of containerEntries) {
100
+ const container: Container = {
101
+ name: containerName,
102
+ ready: (containerData as any).status.ready,
103
+ reestartCount: (containerData as any).status.restarts,
104
+ metrics: {
105
+ cpu: (containerData as any).status.metrics.cpu,
106
+ memory: (containerData as any).status.metrics.memory,
107
+ },
108
+ states: (containerData as any).status.status,
109
+ };
110
+ containers.push(container);
111
+ }
112
+
113
+ usedCpu += Number(instanceData.status.metrics.cpu);
114
+ usedMemory += Number(instanceData.status.metrics.memory);
115
+
116
+ const instance: Instance = {
117
+ id: instanceId,
118
+ name: `${roleName}::${instanceId}`,
119
+ status: instanceData.status.status,
120
+ usage: {
121
+ current: {
122
+ cpu: instanceData.status.metrics.cpu,
123
+ memory: instanceData.status.metrics.memory,
124
+ storage: 0,
125
+ volatileStorage: 0,
126
+ nonReplicatedStorage: 0,
127
+ persistentStorage: 0,
128
+ },
129
+ limit: {
130
+ cpu: {
131
+ max: (eventData.spec.roles[roleName]?.cpu || 0) / 1000,
132
+ min: 0,
133
+ },
134
+ memory: {
135
+ max: (eventData.spec.roles[roleName]?.memory || 0) / 1000,
136
+ min: 0,
137
+ },
138
+ storage: { max: 0, min: 0 },
139
+ volatileStorage: { max: 0, min: 0 },
140
+ nonReplicatedStorage: { max: 0, min: 0 },
141
+ persistentStorage: { max: 0, min: 0 },
142
+ },
143
+ cost: 0,
144
+ },
145
+ logs: [],
146
+ containers: containers,
147
+ };
148
+
149
+ roleInstances.push(instance);
150
+ instances.push(instance);
151
+ }
152
+
153
+ const role: Role = {
154
+ name: roleName,
155
+ instances: roleInstances,
156
+ hsize: (roleData as any).status?.scale || 0,
157
+ ...(existingRole?.scalling && { scalling: existingRole.scalling }),
158
+ ...(existingRole?.category && { category: existingRole.category }),
159
+ };
160
+
161
+ roles.push(role);
162
+ }
163
+
164
+ return { roles, instances, usedCpu, usedMemory };
165
+ };
166
+
167
+ /**
168
+ * Create a new revision object from event data
169
+ */
170
+ const createRevision = (
171
+ entityId: string,
172
+ parentParts: { [entity: string]: string },
173
+ eventData: any,
174
+ usedCpu: number,
175
+ usedMemory: number
176
+ ): Revision => {
177
+ return {
178
+ service: parentParts.service,
179
+ revision: entityId,
180
+ usage: {
181
+ current: {
182
+ cpu: usedCpu,
183
+ memory: usedMemory,
184
+ storage: 0,
185
+ volatileStorage: 0,
186
+ nonReplicatedStorage: 0,
187
+ persistentStorage: 0,
188
+ },
189
+ limit: {
190
+ cpu: {
191
+ max: eventData.spec.intensives?.vcpu / 1000 || 0,
192
+ min: 0,
193
+ },
194
+ memory: {
195
+ max: eventData.spec.intensives?.ram / 1000 || 0,
196
+ min: 0,
197
+ },
198
+ storage: {
199
+ max: eventData.spec.intensives?.shared_disk / 1000 || 0,
200
+ min: 0,
201
+ },
202
+ volatileStorage: {
203
+ max: eventData.spec.intensives?.volatile_disk / 1000 || 0,
204
+ min: 0,
205
+ },
206
+ nonReplicatedStorage: {
207
+ max: eventData.spec.intensives?.nrpersistent_disk / 1000 || 0,
208
+ min: 0,
209
+ },
210
+ persistentStorage: {
211
+ max: eventData.spec.intensives?.persistent_disk / 1000 || 0,
212
+ min: 0,
213
+ },
214
+ },
215
+ cost: 0,
216
+ },
217
+ status: eventData.status.state,
218
+ errorCode:
219
+ eventData.status && eventData.status.error
220
+ ? eventData.status.error.code
221
+ : "",
222
+ errorMsg:
223
+ eventData.status && eventData.status.error
224
+ ? eventData.status.error.message
225
+ : "",
226
+ createdAt:
227
+ (eventData.status && eventData.status.runtime?.status?.createdAt) || "",
228
+ };
229
+ };
230
+
231
+ /**
232
+ * Update service with new revision data
233
+ */
234
+ const updateServiceWithRevision = (
235
+ existingService: Service,
236
+ entityId: string,
237
+ eventData: any,
238
+ newRevision: Revision,
239
+ roles: Role[]
240
+ ): { updatedService: Service; deploymentErrorEvent: Service | null } => {
241
+ const existingRevisionIndex = existingService.revisions.findIndex(
242
+ (rev) => rev === entityId
243
+ );
244
+ const shouldIncludeInList =
245
+ newRevision.errorCode !== "COMPUTE_ERROR" &&
246
+ eventData.status?.error?.code !== "COMPUTE_ERROR";
247
+
248
+ let updatedRevisions;
249
+ if (shouldIncludeInList) {
250
+ updatedRevisions =
251
+ existingRevisionIndex === -1
252
+ ? [...existingService.revisions, entityId]
253
+ : existingService.revisions;
254
+ } else {
255
+ updatedRevisions = existingService.revisions.filter(
256
+ (rev) => rev !== entityId
257
+ );
258
+ }
259
+
260
+ const updatedService: Service = {
261
+ ...existingService,
262
+ revisions: updatedRevisions,
263
+ role: roles.length > 0 ? roles : existingService.role,
264
+ usage: newRevision.usage,
265
+ startedAt: newRevision.createdAt || existingService.startedAt,
266
+ };
267
+
268
+ let deploymentErrorEvent: Service | null = null;
269
+
270
+ const incomingStatus = eventData.status.state;
271
+ const incomingTs = getTimestamp(incomingStatus.timestamp);
272
+ const currentTs = getTimestamp(existingService.status?.timestamp);
273
+
274
+ if (incomingTs > currentTs) {
275
+ updatedService.status = incomingStatus;
276
+
277
+ if (eventData.status.error) {
278
+ updatedService.error = {
279
+ code: eventData.status.error.code,
280
+ message: eventData.status.error.message,
281
+ timestamp: eventData.status.error.timestamp || incomingStatus.timestamp,
282
+ };
283
+ deploymentErrorEvent = updatedService;
284
+ } else {
285
+ updatedService.error = undefined;
286
+ }
287
+ } else if (eventData.status.error) {
288
+ const incomingErrorTs = getTimestamp(eventData.status.error.timestamp);
289
+ const currentErrorTs = getTimestamp(existingService.error?.timestamp);
290
+
291
+ if (incomingErrorTs > currentErrorTs) {
292
+ updatedService.error = {
293
+ code: eventData.status.error.code,
294
+ message: eventData.status.error.message,
295
+ timestamp: eventData.status.error.timestamp,
296
+ };
297
+ deploymentErrorEvent = updatedService;
298
+ }
299
+ }
300
+
301
+ return { updatedService, deploymentErrorEvent };
302
+ };
303
+
304
+ /**
305
+ * Update environment consumption history
306
+ */
307
+ const updateEnvironmentConsumption = (
308
+ environment: Environment,
309
+ servicesMap: Map<string, Service>,
310
+ revisionsMap: Map<string, Revision>,
311
+ serviceEnvironmentId: string
312
+ ): Environment => {
313
+ let totalEnvCpu = 0;
314
+ let totalEnvMemory = 0;
315
+
316
+ servicesMap.forEach((service, svcId) => {
317
+ if (service.environment === serviceEnvironmentId) {
318
+ const currentRevisionKey = `${svcId}-${service.currentRevision}`;
319
+ const currentRevision = revisionsMap.get(currentRevisionKey);
320
+ if (currentRevision) {
321
+ totalEnvCpu += currentRevision.usage.limit.cpu.max || 0;
322
+ totalEnvMemory += currentRevision.usage.limit.memory.max || 0;
323
+ }
324
+ }
325
+ });
326
+
327
+ if (!environment.usage.current.cpuConsuption) {
328
+ environment.usage.current.cpuConsuption = [];
329
+ }
330
+ if (!environment.usage.current.memoryConsuption) {
331
+ environment.usage.current.memoryConsuption = [];
332
+ }
333
+
334
+ environment.usage.current.cpuConsuption.push(totalEnvCpu);
335
+ environment.usage.current.memoryConsuption.push(totalEnvMemory);
336
+
337
+ if (environment.usage.current.cpuConsuption.length > MAX_HISTORY) {
338
+ environment.usage.current.cpuConsuption =
339
+ environment.usage.current.cpuConsuption.slice(-MAX_HISTORY);
340
+ }
341
+ if (environment.usage.current.memoryConsuption.length > MAX_HISTORY) {
342
+ environment.usage.current.memoryConsuption =
343
+ environment.usage.current.memoryConsuption.slice(-MAX_HISTORY);
344
+ }
345
+
346
+ return environment;
347
+ };
348
+
349
+ /**
350
+ * Update account consumption history
351
+ */
352
+ const updateAccountConsumption = (
353
+ account: Account,
354
+ servicesMap: Map<string, Service>,
355
+ revisionsMap: Map<string, Revision>,
356
+ serviceAccountId: string
357
+ ): Account => {
358
+ let totalAccountCpu = 0;
359
+ let totalAccountMemory = 0;
360
+
361
+ servicesMap.forEach((service, svcId) => {
362
+ if (service.account === serviceAccountId) {
363
+ const currentRevisionKey = `${svcId}-${service.currentRevision}`;
364
+ const currentRevision = revisionsMap.get(currentRevisionKey);
365
+ if (currentRevision) {
366
+ totalAccountCpu += currentRevision.usage.limit.cpu.max || 0;
367
+ totalAccountMemory += currentRevision.usage.limit.memory.max || 0;
368
+ }
369
+ }
370
+ });
371
+
372
+ if (!account.usage.current.cpuConsuption) {
373
+ account.usage.current.cpuConsuption = [];
374
+ }
375
+ if (!account.usage.current.memoryConsuption) {
376
+ account.usage.current.memoryConsuption = [];
377
+ }
378
+
379
+ account.usage.current.cpuConsuption.push(totalAccountCpu);
380
+ account.usage.current.memoryConsuption.push(totalAccountMemory);
381
+
382
+ if (account.usage.current.cpuConsuption.length > MAX_HISTORY) {
383
+ account.usage.current.cpuConsuption =
384
+ account.usage.current.cpuConsuption.slice(-MAX_HISTORY);
385
+ }
386
+ if (account.usage.current.memoryConsuption.length > MAX_HISTORY) {
387
+ account.usage.current.memoryConsuption =
388
+ account.usage.current.memoryConsuption.slice(-MAX_HISTORY);
389
+ }
390
+
391
+ return account;
392
+ };
393
+
394
+ // ============================================
395
+ // MAIN HANDLER FUNCTION
396
+ // ============================================
397
+
398
+ /**
399
+ * Handles the "revision" event from WebSocket messages
400
+ * Processes revision data, updates services, environments, and accounts
401
+ */
402
+ export const handleRevisionEvent = ({
403
+ entityId,
404
+ eventData,
405
+ parentParts,
406
+ servicesMap,
407
+ revisionsMap,
408
+ roleMap,
409
+ environmentsMap,
410
+ accountsMap,
411
+ }: HandleRevisionEventParams): HandleRevisionEventResult => {
412
+ const serviceId = `${parentParts.tenant}/${parentParts.service}`;
413
+ const revisionKey = `${serviceId}-${entityId}`;
414
+
415
+ // Process roles and instances
416
+ const { roles, instances, usedCpu, usedMemory } = processRolesAndInstances(
417
+ eventData,
418
+ serviceId,
419
+ roleMap
420
+ );
421
+
422
+ // Create new revision
423
+ const newRevision = createRevision(
424
+ entityId,
425
+ parentParts,
426
+ eventData,
427
+ usedCpu,
428
+ usedMemory
429
+ );
430
+
431
+ // Initialize result
432
+ let updatedService: Service | null = null;
433
+ let updatedEnvironment: Environment | null = null;
434
+ let updatedAccount: Account | null = null;
435
+ let pendingRevisionError: { service: string; revision: Revision } | null = null;
436
+ let serviceDeployedEvent: Service | null = null;
437
+ let serviceDeploymentErrorEvent: Service | null = null;
438
+ let shouldFetchChannels = false;
439
+ let channelsFetchInfo: { serviceId: string; tenantId: string } | null = null;
440
+
441
+ const existingService = servicesMap.get(serviceId);
442
+
443
+ if (existingService) {
444
+ const serviceUpdateResult = updateServiceWithRevision(
445
+ existingService,
446
+ entityId,
447
+ eventData,
448
+ newRevision,
449
+ roles
450
+ );
451
+
452
+ updatedService = serviceUpdateResult.updatedService;
453
+ serviceDeploymentErrorEvent = serviceUpdateResult.deploymentErrorEvent;
454
+ const serviceEnvironmentId = updatedService.environment;
455
+ if (serviceEnvironmentId) {
456
+ const environment = environmentsMap.get(serviceEnvironmentId);
457
+ if (environment) {
458
+ const tempServicesMap = new Map(servicesMap);
459
+ tempServicesMap.set(serviceId, updatedService);
460
+ const tempRevisionsMap = new Map(revisionsMap);
461
+ tempRevisionsMap.set(revisionKey, newRevision);
462
+
463
+ updatedEnvironment = updateEnvironmentConsumption(
464
+ { ...environment },
465
+ tempServicesMap,
466
+ tempRevisionsMap,
467
+ serviceEnvironmentId
468
+ );
469
+ }
470
+ }
471
+ const serviceAccountId = updatedService.account;
472
+ if (serviceAccountId) {
473
+ const account = accountsMap.get(serviceAccountId);
474
+ if (account) {
475
+ const tempServicesMap = new Map(servicesMap);
476
+ tempServicesMap.set(serviceId, updatedService);
477
+
478
+ const tempRevisionsMap = new Map(revisionsMap);
479
+ tempRevisionsMap.set(revisionKey, newRevision);
480
+
481
+ updatedAccount = updateAccountConsumption(
482
+ { ...account },
483
+ tempServicesMap,
484
+ tempRevisionsMap,
485
+ serviceAccountId
486
+ );
487
+ }
488
+ }
489
+ } else if (eventData.status && eventData.status.error) {
490
+ newRevision.errorCode = eventData.status.error.code;
491
+ newRevision.errorMsg = eventData.status.error.message;
492
+ pendingRevisionError = {
493
+ service: serviceId,
494
+ revision: newRevision,
495
+ };
496
+ }
497
+ if (!eventData.meta.deleted && eventData.status && eventData.status.deployed) {
498
+ shouldFetchChannels = true;
499
+ channelsFetchInfo = {
500
+ serviceId: parentParts.service,
501
+ tenantId: parentParts.tenant,
502
+ };
503
+ }
504
+
505
+ return {
506
+ revision: newRevision,
507
+ revisionKey,
508
+ serviceId,
509
+ roles,
510
+ updatedService,
511
+ updatedEnvironment,
512
+ updatedAccount,
513
+ pendingRevisionError,
514
+ shouldFetchChannels,
515
+ channelsFetchInfo,
516
+ serviceDeployedEvent,
517
+ serviceDeploymentErrorEvent,
518
+ };
519
+ };
520
+ export const processRevisionData = (service: Service, revisionData: any): Service => {
521
+ const { solution } = revisionData;
522
+ const deploymentName = solution.top || service.name;
523
+ const deployment = solution.deployments[deploymentName];
524
+
525
+ if (!deployment) {
526
+ console.warn(
527
+ `No deployment found with name ${deploymentName} in solution. Available deployments:`,
528
+ Object.keys(solution.deployments),
529
+ );
530
+ const firstDeploymentKey = Object.keys(solution.deployments)[0];
531
+ if (firstDeploymentKey) {
532
+ console.warn(`Using first available deployment: ${firstDeploymentKey}`);
533
+ return processDeployment(
534
+ service,
535
+ solution.deployments[firstDeploymentKey],
536
+ revisionData,
537
+ );
538
+ }
539
+ return service;
540
+ }
541
+
542
+ return processDeployment(service, deployment, revisionData);
543
+ };
544
+ export const processDeployment = (
545
+ service: Service,
546
+ deployment: any,
547
+ revisionData: any,
548
+ ): Service => {
549
+ const artifact = deployment.artifact;
550
+ const deploymentConfig = deployment.config || {};
551
+ const serviceResources = extractResources(deploymentConfig.resource || {});
552
+ const allServiceParameters = extractParametersFromConfig(
553
+ deploymentConfig.parameter || {},
554
+ );
555
+
556
+ const rolesDefinition = artifact.description?.role || {};
557
+ const hasRoles = Object.keys(rolesDefinition).length > 0;
558
+ let updatedRoles: Role[] = [];
559
+
560
+ if (hasRoles) {
561
+ Object.entries(rolesDefinition).forEach(
562
+ ([roleName, roleData]: [string, any]) => {
563
+ const roleResources = extractResources(roleData.config?.resource || {});
564
+ const existingRole = service.role.find((r) => r.name === roleName);
565
+ let hsize = 1;
566
+ if (roleData.config?.scale?.hsize !== undefined) {
567
+ hsize = roleData.config.scale.hsize;
568
+ }
569
+ if (deploymentConfig.scale?.detail?.[roleName]?.hsize !== undefined) {
570
+ hsize = deploymentConfig.scale.detail[roleName].hsize;
571
+ }
572
+ const hasDuplex =
573
+ roleData.artifact?.description?.srv?.duplex?.length > 0;
574
+ const hasVolumeResource = Object.values(
575
+ roleData.artifact?.description?.config?.resource || {},
576
+ ).some((resourceData: any) => resourceData.volume);
577
+ const role: Role = {
578
+ name: roleName,
579
+ instances: existingRole?.instances || [],
580
+ logo: service.logo,
581
+ description:
582
+ roleData.artifact?.ref?.module || roleData.ref?.module || "",
583
+ resource: roleResources,
584
+ parameters: allServiceParameters,
585
+ hsize: hsize,
586
+ category: hasDuplex || hasVolumeResource ? "stateful" : "",
587
+ };
588
+ if (
589
+ deployment.meta?.scaling &&
590
+ Object.keys(deployment.meta.scaling.simple || {}).length > 0
591
+ ) {
592
+ role.scalling = processScalingConfig(deployment.meta);
593
+ }
594
+
595
+ updatedRoles.push(role);
596
+ },
597
+ );
598
+ } else {
599
+ let hsize = 1;
600
+
601
+ if (deploymentConfig.scale?.hsize !== undefined) {
602
+ hsize = deploymentConfig.scale.hsize;
603
+ }
604
+
605
+ if (deploymentConfig.scale?.detail?.[""]?.hsize !== undefined) {
606
+ hsize = deploymentConfig.scale.detail[""].hsize;
607
+ }
608
+ updatedRoles = [
609
+ {
610
+ name: service.name,
611
+ instances: [],
612
+ logo: service.logo,
613
+ description:
614
+ artifact.ref?.module ||
615
+ (artifact.description?.builtin ? "Builtin Service" : ""),
616
+ resource: serviceResources,
617
+ parameters: allServiceParameters,
618
+ hsize: hsize,
619
+ ...(deployment.meta?.scaling &&
620
+ Object.keys(deployment.meta.scaling.simple || {}).length > 0 && {
621
+ scalling: processScalingConfig(deployment.meta),
622
+ }),
623
+ },
624
+ ];
625
+ }
626
+
627
+ return {
628
+ ...service,
629
+ resources: serviceResources,
630
+ parameters: allServiceParameters,
631
+ role: updatedRoles,
632
+ };
633
+ };
634
+ export const extractParametersFromConfig = (
635
+ parameterConfig: any,
636
+ ): { [key: string]: string }[] => {
637
+ const parameters: { [key: string]: string }[] = [];
638
+
639
+ if (!parameterConfig || typeof parameterConfig !== "object") {
640
+ return parameters;
641
+ }
642
+
643
+ Object.entries(parameterConfig).forEach(
644
+ ([paramName, paramValue]: [string, any]) => {
645
+ let value: string;
646
+ let description: string | undefined;
647
+
648
+ if (typeof paramValue === "object" && paramValue !== null) {
649
+ if (paramValue.value !== undefined) {
650
+ value = String(paramValue.value);
651
+ } else if (paramValue.default !== undefined) {
652
+ value = String(paramValue.default);
653
+ } else {
654
+ value = JSON.stringify(paramValue);
655
+ }
656
+
657
+ if (paramValue.description) {
658
+ description = paramValue.description;
659
+ }
660
+ } else {
661
+ value = String(paramValue);
662
+ }
663
+
664
+ const parameter: { [key: string]: string } = {
665
+ name: paramName,
666
+ value: value,
667
+ type: typeof paramValue,
668
+ configKey: paramName,
669
+ };
670
+
671
+ if (description) {
672
+ parameter.description = description;
673
+ }
674
+
675
+ parameters.push(parameter);
676
+ },
677
+ );
678
+
679
+ return parameters;
680
+ };
681
+ export const extractResources = (resourceConfig: any): Resource[] => {
682
+ const resources: Resource[] = [];
683
+
684
+ Object.entries(resourceConfig).forEach(
685
+ ([resourceName, resourceData]: [string, any]) => {
686
+ let resource: Resource;
687
+ if (resourceData.volume) {
688
+ const volumeData = resourceData.volume;
689
+ const sizeInGB = convertToGigabytes(volumeData.size, volumeData.unit);
690
+ resource = {
691
+ type: "volume",
692
+ name: resourceName,
693
+ value: sizeInGB ? sizeInGB.toString() : "1",
694
+ kind: volumeData.type || "storage",
695
+ status: "available",
696
+ tenant: "",
697
+ };
698
+ } else if (resourceData.port) {
699
+ resource = {
700
+ type: "port",
701
+ name: resourceName,
702
+ value: extractResourceName(resourceData.port),
703
+ status: "available",
704
+ tenant: "",
705
+ };
706
+ } else if (resourceData.domain) {
707
+ resource = {
708
+ type: "domain",
709
+ name: resourceName,
710
+ value: extractResourceName(resourceData.domain),
711
+ status: "available",
712
+ tenant: "",
713
+ };
714
+ } else if (resourceData.secret) {
715
+ resource = {
716
+ type: "secret",
717
+ name: resourceName,
718
+ value: extractResourceName(resourceData.secret),
719
+ status: "available",
720
+ tenant: "",
721
+ };
722
+ } else if (resourceData.certificate) {
723
+ resource = {
724
+ type: "certificate",
725
+ name: resourceName,
726
+ value: extractResourceName(
727
+ resourceData.certificate.cert || resourceData.certificate,
728
+ ),
729
+ key: resourceData.key,
730
+ domain: resourceData.domain,
731
+ status: "available",
732
+ tenant: "",
733
+ };
734
+ } else if (resourceData.ca) {
735
+ resource = {
736
+ type: "ca",
737
+ name: resourceName,
738
+ value: extractResourceName(resourceData.ca),
739
+ status: "available",
740
+ tenant: "",
741
+ };
742
+ } else {
743
+ resource = {
744
+ type: resourceData.type || "unknown",
745
+ name: resourceName,
746
+ value: extractResourceName(
747
+ resourceData.value || resourceData.size || "",
748
+ ),
749
+ status: "available",
750
+ tenant: "",
751
+ };
752
+
753
+ if (resourceData.kind) resource.kind = resourceData.kind;
754
+ if (resourceData.domain) resource.domain = resourceData.domain;
755
+ if (resourceData.key) resource.key = resourceData.key;
756
+ if (resourceData.maxItems) resource.maxItems = resourceData.maxItems;
757
+ }
758
+
759
+ resources.push(resource);
760
+ },
761
+ );
762
+
763
+ return resources;
764
+ };
765
+ const processScalingConfig = (meta: any): any => {
766
+ if (!meta?.scaling?.simple || Object.keys(meta.scaling.simple).length === 0) {
767
+ return {
768
+ cpu: { up: "80%", down: "20%" },
769
+ memory: { up: "80%", down: "20%" },
770
+ instances: { max: 1, min: 1 },
771
+ histeresys: "5",
772
+ };
773
+ }
774
+
775
+ const roleNames = Object.keys(meta.scaling.simple);
776
+ const firstRoleName = roleNames[0];
777
+ const roleScaling = meta.scaling.simple[firstRoleName];
778
+
779
+ if (!roleScaling) {
780
+ return {
781
+ cpu: { up: "80%", down: "20%" },
782
+ memory: { up: "80%", down: "20%" },
783
+ instances: { max: 1, min: 1 },
784
+ histeresys: "5",
785
+ };
786
+ }
787
+
788
+ return {
789
+ cpu: {
790
+ up: `${roleScaling.scale_up?.cpu || 80}%`,
791
+ down: `${roleScaling.scale_down?.cpu || 20}%`,
792
+ },
793
+ memory: {
794
+ up: `${roleScaling.scale_up?.memory || 80}%`,
795
+ down: `${roleScaling.scale_down?.memory || 20}%`,
796
+ },
797
+ instances: {
798
+ max: roleScaling.max_replicas || 1,
799
+ min: roleScaling.min_replicas || 1,
800
+ },
801
+ histeresys: `${roleScaling.hysteresis || 5}`,
802
+ };
803
+ };
804
+ const extractResourceName = (resourcePath: string): string => {
805
+ if (!resourcePath) return "";
806
+
807
+ const parts = resourcePath.split("/");
808
+
809
+ if (parts.length === 2) {
810
+ if (parts[0] === "cluster.core") {
811
+ return resourcePath;
812
+ }
813
+ return parts[1];
814
+ }
815
+ return resourcePath;
816
+ };
817
+ //UNUSED KEEPING IT JUST IN CASE:
818
+ const extractParameters = (
819
+ parameterConfig: any,
820
+ roleData?: any,
821
+ roleName?: string,
822
+ ): { [key: string]: string }[] => {
823
+ const parameters: { [key: string]: string }[] = [];
824
+
825
+ if (roleData?.artifact?.description?.code) {
826
+ const codeEntries = Object.entries(roleData.artifact.description.code);
827
+
828
+ for (const [codeName, codeData] of codeEntries) {
829
+ const mapping = (codeData as any)?.mapping?.env;
830
+
831
+ if (mapping && typeof mapping === "object") {
832
+ Object.entries(mapping).forEach(([envName, envData]: [string, any]) => {
833
+ const envValue = envData.value || "";
834
+ let configKey = envName;
835
+ const matchedParam = Object.entries(parameterConfig).find(
836
+ ([configParamName, paramData]: [string, any]) => {
837
+ const configValue =
838
+ typeof paramData === "object"
839
+ ? paramData.value || paramData.default || ""
840
+ : String(paramData);
841
+ const envLower = envName.toLowerCase();
842
+ const configLower = configParamName.toLowerCase();
843
+
844
+ if (
845
+ envLower.includes(configLower) ||
846
+ configLower.includes(envLower)
847
+ ) {
848
+ return configValue === envValue;
849
+ }
850
+
851
+ return false;
852
+ },
853
+ );
854
+
855
+ if (matchedParam) {
856
+ configKey = matchedParam[0];
857
+ }
858
+
859
+ const parameter: { [key: string]: string } = {
860
+ name: envName,
861
+ value: envValue,
862
+ type: configKey,
863
+ configKey: configKey,
864
+ ...(roleName && { fromRole: roleName }),
865
+ };
866
+
867
+ parameters.push(parameter);
868
+ });
869
+ }
870
+ }
871
+ }
872
+
873
+ if (parameters.length === 0 && parameterConfig) {
874
+ Object.entries(parameterConfig).forEach(
875
+ ([paramName, paramData]: [string, any]) => {
876
+ const paramValue =
877
+ typeof paramData === "object"
878
+ ? paramData.value || paramData.default || ""
879
+ : String(paramData);
880
+
881
+ const parameter: { [key: string]: string } = {
882
+ name: paramName,
883
+ value: paramValue,
884
+ type: paramName,
885
+ configKey: paramName,
886
+ ...(roleName && { fromRole: roleName }),
887
+ };
888
+
889
+ if (typeof paramData === "object" && paramData.description) {
890
+ parameter.description = paramData.description;
891
+ }
892
+
893
+ parameters.push(parameter);
894
+ },
895
+ );
896
+ }
897
+
898
+ return parameters;
899
+ };