@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.
- package/.gitlab-ci.yml +57 -0
- package/README.md +352 -0
- package/api/account-api-service.ts +1092 -0
- package/api/deploy-service-helper.ts +1611 -0
- package/api/environment-api-service.ts +542 -0
- package/api/marketplace-api-service.ts +1031 -0
- package/api/organizations-api-service.ts +0 -0
- package/api/planProvider-api-service.ts +24 -0
- package/api/reporting-api-service.ts +35 -0
- package/api/resources-api-service.ts +821 -0
- package/api/service-api-service.ts +796 -0
- package/api/tenant-api-service.ts +1260 -0
- package/api/user-api-service.ts +1161 -0
- package/backend-handler.ts +1127 -0
- package/environment.ts +7 -0
- package/event-helper.ts +577 -0
- package/event-names.ts +152 -0
- package/helpers/account-helper.ts +331 -0
- package/helpers/environment-helper.ts +289 -0
- package/helpers/link-helper.ts +114 -0
- package/helpers/plan-helper.ts +104 -0
- package/helpers/registry-helper.ts +134 -0
- package/helpers/resource-helper.ts +387 -0
- package/helpers/revision-helper.ts +899 -0
- package/helpers/service-helper.ts +627 -0
- package/helpers/tenant-helper.ts +191 -0
- package/helpers/token-helper.ts +107 -0
- package/helpers/user-helper.ts +140 -0
- package/jest.config.ts +40 -0
- package/jest.setup.js +4 -0
- package/package.json +50 -0
- package/test/backend-handler.test.ts +792 -0
- package/test/deploy-service-helper.test.ts +518 -0
- package/test/event-helper.test.ts +152 -0
- package/tsconfig.json +26 -0
- package/utils/utils.ts +78 -0
- package/websocket-manager.ts +1833 -0
|
@@ -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
|
+
};
|