@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,289 @@
1
+ import { Account, Environment, Notification } from "@hestekumori/aurora-interfaces";
2
+
3
+
4
+
5
+ interface HandleEnvironmentEventParams {
6
+ entityId: string;
7
+ eventData: any;
8
+ parentParts: { [entity: string]: string };
9
+ accountsMap: Map<string, Account>;
10
+ environmentsMap: Map<string, Environment>;
11
+ pendingCloudProviderUpdates: Array<{ environmentId: string; accountId: string }>;
12
+ }
13
+
14
+ interface HandleEnvironmentEventResult {
15
+ environment: Environment;
16
+ pendingCloudProviderUpdate: { environmentId: string; accountId: string } | null;
17
+ hasTenant: boolean;
18
+ tenantId: string;
19
+ }
20
+ interface HandleEnvironmentOperationSuccessParams {
21
+ action: string;
22
+ entityName: string;
23
+ originalData: Environment;
24
+ }
25
+
26
+ interface HandleEnvironmentOperationSuccessResult {
27
+ updatedEnvironment: Environment | null;
28
+ shouldDelete: boolean;
29
+ notification: Notification | null;
30
+ eventType: "created" | "updated" | "cleaned" | null;
31
+ }
32
+ interface HandleEnvironmentOperationErrorParams {
33
+ action: string;
34
+ entityName: string;
35
+ originalData: Environment;
36
+ error: any;
37
+ }
38
+
39
+ interface HandleEnvironmentOperationErrorResult {
40
+ updatedEnvironment: Environment;
41
+ notification: Notification;
42
+ eventType: "creationError" | "updateError" | "deletionError";
43
+ }
44
+
45
+ /**
46
+ * Handles the "environment" event from WebSocket messages
47
+ * Processes environment data updates and cloud provider associations
48
+ */
49
+ export const handleEnvironmentEvent = ({
50
+ entityId,
51
+ eventData,
52
+ parentParts,
53
+ accountsMap,
54
+ environmentsMap,
55
+ pendingCloudProviderUpdates,
56
+ }: HandleEnvironmentEventParams): HandleEnvironmentEventResult => {
57
+ const tenantId = parentParts.tenant;
58
+ const accountId = parentParts.account;
59
+ const relatedAccount = accountsMap.get(accountId);
60
+ let cloudProviderName = relatedAccount?.cloudProvider?.name || "";
61
+
62
+ let pendingCloudProviderUpdate: { environmentId: string; accountId: string } | null = null;
63
+
64
+ if (!relatedAccount) {
65
+ pendingCloudProviderUpdate = {
66
+ environmentId: entityId,
67
+ accountId: accountId,
68
+ };
69
+ }
70
+
71
+ const existingEnvironment = environmentsMap.get(entityId);
72
+
73
+ const newEnvironment: Environment = {
74
+ id: entityId,
75
+ name: entityId,
76
+ account: accountId,
77
+ tenant: tenantId,
78
+ logo: "",
79
+ services: [],
80
+ domains: [],
81
+ status: eventData.meta?.deleted
82
+ ? {
83
+ code: "DELETING",
84
+ message: `Environment ${entityId} is being deleted.`,
85
+ timestamp: new Date().toISOString(),
86
+ }
87
+ : {
88
+ code: eventData.status.state.code,
89
+ message: eventData.status.state.message,
90
+ timestamp: eventData.status.state.timestamp,
91
+ },
92
+ highAvaliable: eventData.spec.highlyAvailable || false,
93
+ usage: {
94
+ current: {
95
+ cpu: eventData.status.marks.vcpu.current / 1000,
96
+ memory: eventData.status.marks.memory.current / 1000,
97
+ storage: eventData.status.marks.storage.current / 1000,
98
+ volatileStorage: eventData.status.marks.vstorage.current / 1000,
99
+ nonReplicatedStorage: eventData.status.marks.nrstorage.current / 1000,
100
+ persistentStorage: eventData.status.marks.rstorage.current / 1000,
101
+ nodes: eventData.status.nodes.length || 0,
102
+ memoryConsuption: existingEnvironment?.usage.current.memoryConsuption || [],
103
+ cpuConsuption: existingEnvironment?.usage.current.cpuConsuption || [],
104
+ },
105
+ limit: {
106
+ cpu: {
107
+ max: eventData.spec.marks.vcpu.highmark / 1000,
108
+ min: eventData.spec.marks.vcpu.lowmark / 1000,
109
+ },
110
+ memory: {
111
+ max: eventData.spec.marks.memory.highmark / 1000,
112
+ min: eventData.spec.marks.memory.lowmark / 1000,
113
+ },
114
+ storage: {
115
+ max: eventData.spec.marks.storage.highmark / 1000,
116
+ min: eventData.spec.marks.storage.lowmark / 1000,
117
+ },
118
+ volatileStorage: {
119
+ max: eventData.spec.marks.vstorage.highmark / 1000,
120
+ min: eventData.spec.marks.vstorage.lowmark / 1000,
121
+ },
122
+ nonReplicatedStorage: {
123
+ max: eventData.spec.marks.nrstorage.highmark / 1000,
124
+ min: eventData.spec.marks.nrstorage.lowmark / 1000,
125
+ },
126
+ persistentStorage: {
127
+ max: eventData.spec.marks.rstorage.highmark / 1000,
128
+ min: eventData.spec.marks.rstorage.lowmark / 1000,
129
+ },
130
+ },
131
+ cost: eventData.status.marks.cost.current,
132
+ },
133
+ organization: "",
134
+ cloudProvider: cloudProviderName,
135
+ labels: Object.values(eventData.meta.labels),
136
+ nodes: {
137
+ max: eventData.spec.marks.nodes.highmark,
138
+ min: eventData.status.minimumRequiredNodes?.ingress?.replicas || 0,
139
+ },
140
+ type: eventData.spec.type || "primary",
141
+ cluster: { name: eventData.spec.cluster?.name || "" },
142
+ isIsolated: eventData.spec.isolated || false,
143
+ };
144
+
145
+ return {
146
+ environment: newEnvironment,
147
+ pendingCloudProviderUpdate,
148
+ hasTenant: false,
149
+ tenantId,
150
+ };
151
+ };
152
+
153
+ /**
154
+ * Handles successful environment operations (CREATE, UPDATE, DELETE, CLEAN)
155
+ */
156
+ export const handleEnvironmentOperationSuccess = ({
157
+ action,
158
+ entityName,
159
+ originalData,
160
+ }: HandleEnvironmentOperationSuccessParams): HandleEnvironmentOperationSuccessResult => {
161
+ const envData = originalData;
162
+
163
+ if (action === "DELETE") {
164
+ return {
165
+ updatedEnvironment: null,
166
+ shouldDelete: true,
167
+ notification: null,
168
+ eventType: null,
169
+ };
170
+ }
171
+
172
+ if (action === "CLEAN") {
173
+ const updatedEnvironment: Environment = {
174
+ ...envData,
175
+ status: {
176
+ code: "CLEANING_ENVIRONMENT",
177
+ message: `Environment ${envData.name} is being cleaned.`,
178
+ timestamp: new Date().toISOString(),
179
+ },
180
+ };
181
+
182
+ const envNotification: Notification = {
183
+ type: "success",
184
+ subtype: "environment-cleaned",
185
+ date: Date.now().toString(),
186
+ status: "unread",
187
+ callToAction: false,
188
+ data: {
189
+ environment: envData.name,
190
+ account: envData.account,
191
+ tenant: envData.tenant,
192
+ },
193
+ };
194
+
195
+ return {
196
+ updatedEnvironment,
197
+ shouldDelete: false,
198
+ notification: envNotification,
199
+ eventType: "cleaned",
200
+ };
201
+ }
202
+ if (envData) {
203
+ const updatedEnvironment: Environment = {
204
+ ...envData,
205
+ status: {
206
+ code: "ENVIRONMENT_READY",
207
+ message: `Environment ${envData.name} is ready.`,
208
+ timestamp: new Date().toISOString(),
209
+ },
210
+ };
211
+
212
+ let eventType: "created" | "updated" | null = null;
213
+ if (action === "CREATE") {
214
+ eventType = "created";
215
+ } else if (action === "UPDATE") {
216
+ eventType = "updated";
217
+ }
218
+
219
+ return {
220
+ updatedEnvironment,
221
+ shouldDelete: false,
222
+ notification: null,
223
+ eventType,
224
+ };
225
+ }
226
+
227
+ return {
228
+ updatedEnvironment: null,
229
+ shouldDelete: false,
230
+ notification: null,
231
+ eventType: null,
232
+ };
233
+ };
234
+ /**
235
+ * Handles failed environment operations (CREATE, UPDATE, DELETE)
236
+ */
237
+ export const handleEnvironmentOperationError = ({
238
+ action,
239
+ entityName,
240
+ originalData,
241
+ error,
242
+ }: HandleEnvironmentOperationErrorParams): HandleEnvironmentOperationErrorResult => {
243
+ let subtype: string;
244
+ let eventType: "creationError" | "updateError" | "deletionError";
245
+
246
+ if (action === "CREATE") {
247
+ subtype = "environment-creation-error";
248
+ eventType = "creationError";
249
+ } else if (action === "UPDATE") {
250
+ subtype = "environment-update-error";
251
+ eventType = "updateError";
252
+ } else {
253
+ subtype = "environment-deletion-error";
254
+ eventType = "deletionError";
255
+ }
256
+
257
+ const envErrorNotification: Notification = {
258
+ type: "error",
259
+ subtype,
260
+ date: Date.now().toString(),
261
+ status: "unread",
262
+ info_content: {
263
+ code: error?.error?.code || "UNKNOWN_ERROR",
264
+ message: error?.error?.content || error?.error?.message || "Unknown error",
265
+ timestamp: error?.error?.timestamp || "",
266
+ },
267
+ callToAction: false,
268
+ data: {
269
+ environment: originalData.name,
270
+ account: originalData.account,
271
+ tenant: originalData.tenant,
272
+ },
273
+ };
274
+
275
+ const updatedEnvironment: Environment = {
276
+ ...originalData,
277
+ status: {
278
+ code: "ERROR",
279
+ message: error?.error?.content || error?.error?.message || "Operation failed",
280
+ timestamp: new Date().toISOString(),
281
+ },
282
+ };
283
+
284
+ return {
285
+ updatedEnvironment,
286
+ notification: envErrorNotification,
287
+ eventType,
288
+ };
289
+ };
@@ -0,0 +1,114 @@
1
+ import { Link, Service } from "@hestekumori/aurora-interfaces";
2
+
3
+
4
+ interface HandleLinkEventParams {
5
+ eventData: any;
6
+ servicesMap: Map<string, Service>;
7
+ }
8
+
9
+ interface HandleLinkEventResult {
10
+ linkId: string;
11
+ clientToServerLink: Link;
12
+ serverToClientLink: Link;
13
+ linkServiceServer: string;
14
+ linkServiceClient: string;
15
+ updatedServerService: Service | null;
16
+ updatedClientService: Service | null;
17
+ serverServiceFound: boolean;
18
+ clientServiceFound: boolean;
19
+ }
20
+ /**
21
+ * Handles the "link" event from WebSocket messages
22
+ * Creates bidirectional links between services
23
+ */
24
+ export const handleLinkEvent = ({
25
+ eventData,
26
+ servicesMap,
27
+ }: HandleLinkEventParams): HandleLinkEventResult => {
28
+ const linkId = eventData.id.name;
29
+ const clientService = eventData.spec.client_service;
30
+ const clientChannel = eventData.spec.client_channel;
31
+ const serverService = eventData.spec.server_service;
32
+ const serverChannel = eventData.spec.server_channel;
33
+ const linkServiceServer = `${eventData.spec.server_tenant}/${serverService}`;
34
+ const linkServiceClient = `${eventData.spec.client_tenant}/${clientService}`;
35
+ const clientToServerLink: Link = {
36
+ name: linkId,
37
+ origin: clientService,
38
+ target: serverService,
39
+ originChannel: clientChannel,
40
+ targetChannel: serverChannel,
41
+ client: clientChannel,
42
+ server: serverChannel,
43
+ };
44
+
45
+ const serverToClientLink: Link = {
46
+ name: linkId,
47
+ origin: clientService,
48
+ target: serverService,
49
+ originChannel: clientChannel,
50
+ targetChannel: serverChannel,
51
+ client: clientChannel,
52
+ server: serverChannel,
53
+ };
54
+ let updatedServerService: Service | null = null;
55
+ let serverServiceFound = false;
56
+
57
+ const serverServiceObj = servicesMap.get(linkServiceServer);
58
+ if (serverServiceObj) {
59
+ serverServiceFound = true;
60
+ const existingLinkIndex = serverServiceObj.links.findIndex(
61
+ (link) =>
62
+ link.name === clientToServerLink.name &&
63
+ link.origin === clientToServerLink.origin &&
64
+ link.target === clientToServerLink.target
65
+ );
66
+
67
+ if (existingLinkIndex !== -1) {
68
+ serverServiceObj.links[existingLinkIndex] = clientToServerLink;
69
+ } else {
70
+ serverServiceObj.links.push(clientToServerLink);
71
+ }
72
+ updatedServerService = serverServiceObj;
73
+ } else {
74
+ console.warn(
75
+ `Client service ${linkServiceServer} not found when processing link event`
76
+ );
77
+ }
78
+ let updatedClientService: Service | null = null;
79
+ let clientServiceFound = false;
80
+
81
+ const clientServiceObj = servicesMap.get(linkServiceClient);
82
+ if (clientServiceObj) {
83
+ clientServiceFound = true;
84
+ const existingLinkIndex = clientServiceObj.links.findIndex(
85
+ (link) =>
86
+ link.name === serverToClientLink.name &&
87
+ link.origin === serverToClientLink.origin &&
88
+ link.target === serverToClientLink.target
89
+ );
90
+
91
+ if (existingLinkIndex !== -1) {
92
+ clientServiceObj.links[existingLinkIndex] = serverToClientLink;
93
+ } else {
94
+ clientServiceObj.links.push(serverToClientLink);
95
+ }
96
+ updatedClientService = clientServiceObj;
97
+ } else {
98
+ console.warn(
99
+ `Server service ${linkServiceClient} not found when processing link event`
100
+ );
101
+ }
102
+
103
+ return {
104
+ linkId,
105
+ clientToServerLink,
106
+ serverToClientLink,
107
+ linkServiceServer,
108
+ linkServiceClient,
109
+ updatedServerService,
110
+ updatedClientService,
111
+ serverServiceFound,
112
+ clientServiceFound,
113
+ };
114
+ };
@@ -0,0 +1,104 @@
1
+ import { Plan, UserData } from "@hestekumori/aurora-interfaces";
2
+
3
+
4
+ interface HandlePlanInstanceEventParams {
5
+ entityId: string;
6
+ eventData: any;
7
+ parentParts: { [entity: string]: string };
8
+ }
9
+
10
+ interface HandlePlanInstanceEventResult {
11
+ plan: Plan;
12
+ planKey: string;
13
+ }
14
+ interface UpdateUserPlansParams {
15
+ newPlan: Plan;
16
+ userData: UserData;
17
+ userId: string;
18
+ }
19
+
20
+ interface UpdateUserPlansResult {
21
+ shouldUpdate: boolean;
22
+ updatedPlans: Plan[];
23
+ }
24
+
25
+ /**
26
+ * Handles the "planinstance" event from WebSocket messages
27
+ * Processes plan data and builds the Plan object
28
+ */
29
+ export const handlePlanInstanceEvent = ({
30
+ entityId,
31
+ eventData,
32
+ parentParts,
33
+ }: HandlePlanInstanceEventParams): HandlePlanInstanceEventResult => {
34
+ const planProvider = parentParts.planprovider;
35
+ const planKey = `${planProvider}/${entityId}`;
36
+
37
+ const newPlan: Plan = {
38
+ name: entityId,
39
+ provider: planProvider,
40
+ alias: eventData.spec.alias || "",
41
+ lastCheck: eventData.spec.lastCheck
42
+ ? eventData.spec.lastCheck.toString()
43
+ : "",
44
+ tenants: eventData.status.tenants
45
+ ? Object.keys(eventData.status.tenants)
46
+ : [],
47
+ quarantine: eventData.spec.quarantine || false,
48
+ owner: eventData.spec.owner || "",
49
+ limits: eventData.spec.limits
50
+ ? {
51
+ cpu: eventData.spec.limits.vcpu || 0,
52
+ memory: eventData.spec.limits.memory || 0,
53
+ instances: eventData.spec.limits.instances || 0,
54
+ deployments: eventData.spec.limits.deployments || 0,
55
+ tenants: eventData.spec.limits.tenants || 0,
56
+ accounts: eventData.spec.limits.accounts || 0,
57
+ clusters: eventData.spec.limits.clusters || 0,
58
+ nrstorage: eventData.spec.limits.nrstorage || 0,
59
+ rstorage: eventData.spec.limits.rstorage || 0,
60
+ vstorage: eventData.spec.limits.vstorage || 0,
61
+ storage: eventData.spec.limits.storage || 0,
62
+ }
63
+ : undefined,
64
+ type: eventData.spec.type || "",
65
+ };
66
+
67
+ return {
68
+ plan: newPlan,
69
+ planKey,
70
+ };
71
+ };
72
+
73
+ /**
74
+ * Updates user plans after a plan event
75
+ * Only updates if the user is the owner of the plan
76
+ */
77
+ export const updateUserPlansAfterPlanEvent = ({
78
+ newPlan,
79
+ userData,
80
+ userId,
81
+ }: UpdateUserPlansParams): UpdateUserPlansResult => {
82
+ if (newPlan.owner !== userId) {
83
+ return {
84
+ shouldUpdate: false,
85
+ updatedPlans: userData.plans || [],
86
+ };
87
+ }
88
+
89
+ const updatedPlans = userData.plans || [];
90
+ const existingIndex = updatedPlans.findIndex(
91
+ (plan) => plan.name === newPlan.name && plan.provider === newPlan.provider
92
+ );
93
+
94
+ if (existingIndex !== -1) {
95
+ updatedPlans[existingIndex] = newPlan;
96
+ } else {
97
+ updatedPlans.push(newPlan);
98
+ }
99
+
100
+ return {
101
+ shouldUpdate: true,
102
+ updatedPlans,
103
+ };
104
+ };
@@ -0,0 +1,134 @@
1
+
2
+ import { Registry, Tenant } from "@hestekumori/aurora-interfaces";
3
+ import { decodeRegistryAuth } from "../utils/utils";
4
+
5
+
6
+ interface HandleRegistryEventParams {
7
+ eventData: any;
8
+ parentParts: { [entity: string]: string };
9
+ tenantsMap: Map<string, Tenant>;
10
+ secretsMap: Map<string, any>;
11
+ }
12
+
13
+ interface HandleRegistryEventResult {
14
+ registry: Registry;
15
+ tenantId: string;
16
+ tenantFound: boolean;
17
+ updatedTenant: Tenant | null;
18
+ pendingRegistry: { tenant: string; registry: Registry } | null;
19
+ }
20
+ /**
21
+ * Extract registry credentials from secrets map
22
+ */
23
+ const extractRegistryCredentials = (
24
+ tenantId: string,
25
+ secretName: string,
26
+ secretsMap: Map<string, any>
27
+ ): { username: string; password: string } => {
28
+ let username = "";
29
+ let password = "";
30
+
31
+ const secretKey = `${tenantId}/${secretName}`;
32
+ const credentialsKey = `${secretKey}_credentials`;
33
+ const savedCredentials = secretsMap.get(credentialsKey);
34
+ if (savedCredentials) {
35
+ username = savedCredentials.secretUsername;
36
+ password = savedCredentials.secretPassword;
37
+ } else {
38
+ const secretData = secretsMap.get(secretKey);
39
+ if (secretData) {
40
+ try {
41
+ const parsedSecret = JSON.parse(secretData);
42
+ if (parsedSecret.auths) {
43
+ const registryUrl = Object.keys(parsedSecret.auths)[0];
44
+ if (registryUrl && parsedSecret.auths[registryUrl].auth) {
45
+ const credentials = decodeRegistryAuth(
46
+ parsedSecret.auths[registryUrl].auth
47
+ );
48
+ username = credentials.username;
49
+ password = credentials.password;
50
+ }
51
+ }
52
+ } catch (error) {
53
+ console.error("Error parsing registry secret in dregistry:", error);
54
+ }
55
+ } else {
56
+ console.warn("Secret not found for registry:", {
57
+ registryName: secretName,
58
+ secretKey,
59
+ availableSecrets: Array.from(secretsMap.keys()),
60
+ });
61
+ }
62
+ }
63
+
64
+ return { username, password };
65
+ };
66
+
67
+ /**
68
+ * Handles the "dregistry" event from WebSocket messages
69
+ * Processes registry data and credentials
70
+ */
71
+ export const handleRegistryEvent = ({
72
+ eventData,
73
+ parentParts,
74
+ tenantsMap,
75
+ secretsMap,
76
+ }: HandleRegistryEventParams): HandleRegistryEventResult => {
77
+ const tenantId = parentParts.tenant;
78
+ const registryName = eventData.id?.name;
79
+ let username = "";
80
+ let password = "";
81
+
82
+ if (eventData.status?.secret?.name) {
83
+ const credentials = extractRegistryCredentials(
84
+ tenantId,
85
+ eventData.status.secret.name,
86
+ secretsMap
87
+ );
88
+ username = credentials.username;
89
+ password = credentials.password;
90
+ }
91
+ const newRegistry: Registry = {
92
+ name: registryName,
93
+ domain: eventData.spec.registry || "",
94
+ public: true,
95
+ credentials: username,
96
+ password: password,
97
+ description: `Registry for ${registryName}`,
98
+ extra: eventData.meta.labels.logo || "",
99
+ };
100
+ const tenant = tenantsMap.get(tenantId);
101
+ let tenantFound = false;
102
+ let updatedTenant: Tenant | null = null;
103
+ let pendingRegistry: { tenant: string; registry: Registry } | null = null;
104
+
105
+ if (tenant) {
106
+ tenantFound = true;
107
+ const existingIndex = tenant.registry.findIndex(
108
+ (reg) => reg.domain === registryName
109
+ );
110
+
111
+ if (existingIndex !== -1) {
112
+ tenant.registry[existingIndex] = newRegistry;
113
+ } else {
114
+ tenant.registry.push(newRegistry);
115
+ }
116
+ updatedTenant = tenant;
117
+ } else {
118
+ pendingRegistry = {
119
+ tenant: tenantId,
120
+ registry: newRegistry,
121
+ };
122
+ console.warn(
123
+ `Registry orphaned for tenant ${tenantId}, added to pending registries.`
124
+ );
125
+ }
126
+
127
+ return {
128
+ registry: newRegistry,
129
+ tenantId,
130
+ tenantFound,
131
+ updatedTenant,
132
+ pendingRegistry,
133
+ };
134
+ };