@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,387 @@
1
+
2
+ import { Resource, Tenant } from "@hestekumori/aurora-interfaces";
3
+ import { decodeRegistryAuth } from "../utils/utils";
4
+
5
+
6
+ type ResourceType = "domain" | "port" | "secret" | "certificate" | "ca" | "volume";
7
+
8
+ interface HandleResourceEventParams {
9
+ kind: ResourceType;
10
+ eventData: any;
11
+ parentParts: { [entity: string]: string };
12
+ tenantsMap: Map<string, Tenant>;
13
+ secretsMap: Map<string, any>;
14
+ hasRequestingServices: boolean;
15
+ }
16
+
17
+ interface HandleResourceEventResult {
18
+ resource: Resource;
19
+ tenantId: string;
20
+ tenantFound: boolean;
21
+ updatedTenant: Tenant | null;
22
+ pendingResource: Resource | null;
23
+ publishUpdate: boolean;
24
+ existingResource: Resource | null;
25
+ secretsToStore: Array<{ key: string; value: any }>;
26
+ }
27
+
28
+ // ============================================
29
+ // RESOURCE BUILDERS
30
+ // ============================================
31
+
32
+ /**
33
+ * Build a domain resource
34
+ */
35
+ const buildDomainResource = (
36
+ eventData: any,
37
+ tenantId: string,
38
+ hasRequestingServices: boolean
39
+ ): Resource => {
40
+ return {
41
+ type: "domain",
42
+ name: eventData.id?.name,
43
+ value: eventData.spec.domain,
44
+ status: hasRequestingServices ? "used" : "available",
45
+ tenant: tenantId,
46
+ };
47
+ };
48
+
49
+ /**
50
+ * Build a port resource
51
+ */
52
+ const buildPortResource = (
53
+ eventData: any,
54
+ tenantId: string,
55
+ hasRequestingServices: boolean
56
+ ): Resource => {
57
+ return {
58
+ type: "port",
59
+ name: eventData.id?.name,
60
+ value: eventData.spec.port,
61
+ status: hasRequestingServices ? "used" : "available",
62
+ tenant: tenantId,
63
+ };
64
+ };
65
+
66
+ /**
67
+ * Build a secret resource and extract credentials if applicable
68
+ */
69
+ const buildSecretResource = (
70
+ eventData: any,
71
+ tenantId: string,
72
+ secretsMap: Map<string, any>
73
+ ): { resource: Resource; secretsToStore: Array<{ key: string; value: any }> } => {
74
+ const secretName = eventData.id?.name;
75
+ const secretKey = `${tenantId}/${secretName}`;
76
+ const secretsToStore: Array<{ key: string; value: any }> = [];
77
+ secretsToStore.push({ key: secretKey, value: eventData.spec.secret });
78
+ if (eventData.spec?.secret) {
79
+ try {
80
+ const secretContent = eventData.spec.secret.trim();
81
+
82
+ if (secretContent.startsWith("{") || secretContent.startsWith("[")) {
83
+ const parsedSecret = JSON.parse(secretContent);
84
+
85
+ if (parsedSecret?.auths && typeof parsedSecret.auths === "object") {
86
+ const registryUrl = Object.keys(parsedSecret.auths)[0];
87
+ if (registryUrl && parsedSecret.auths[registryUrl]?.auth) {
88
+ const authString = parsedSecret.auths[registryUrl].auth;
89
+ const credentials = decodeRegistryAuth(authString);
90
+ const credentialsKey = `${secretKey}_credentials`;
91
+ secretsToStore.push({
92
+ key: credentialsKey,
93
+ value: {
94
+ secretUsername: credentials.username,
95
+ secretPassword: credentials.password,
96
+ registryUrl,
97
+ },
98
+ });
99
+ }
100
+ }
101
+ }
102
+ } catch (error) {
103
+ if (
104
+ eventData.spec.secret.trim().startsWith("{") ||
105
+ eventData.spec.secret.trim().startsWith("[")
106
+ ) {
107
+ console.error("Error parsing secret JSON:", {
108
+ secretName,
109
+ error: error,
110
+ secretContent: eventData.spec.secret,
111
+ });
112
+ }
113
+ }
114
+ }
115
+
116
+ const resource: Resource = {
117
+ type: "secret",
118
+ name: secretName,
119
+ value: eventData.spec.secret,
120
+ status: "available",
121
+ tenant: tenantId,
122
+ };
123
+
124
+ return { resource, secretsToStore };
125
+ };
126
+
127
+ /**
128
+ * Build a certificate resource
129
+ */
130
+ const buildCertificateResource = (
131
+ eventData: any,
132
+ tenantId: string
133
+ ): Resource => {
134
+ return {
135
+ type: "certificate",
136
+ name: eventData.id?.name,
137
+ value: eventData.spec.certificate.cert,
138
+ key: eventData.spec.certificate.key,
139
+ domain: eventData.spec.certificate.domain,
140
+ status: "available",
141
+ tenant: tenantId,
142
+ };
143
+ };
144
+
145
+ /**
146
+ * Build a CA resource
147
+ */
148
+ const buildCAResource = (
149
+ eventData: any,
150
+ tenantId: string
151
+ ): Resource => {
152
+ return {
153
+ type: "ca",
154
+ name: eventData.id?.name,
155
+ value: eventData.spec.ca,
156
+ status: "available",
157
+ tenant: tenantId,
158
+ };
159
+ };
160
+
161
+ /**
162
+ * Build a volume resource
163
+ */
164
+ const buildVolumeResource = (
165
+ eventData: any,
166
+ tenantId: string
167
+ ): Resource => {
168
+ return {
169
+ type: "volume",
170
+ name: eventData.id?.name,
171
+ value: eventData.spec.volume.size,
172
+ kind:
173
+ eventData.spec.volume.kind === "shared"
174
+ ? "volatile"
175
+ : eventData.spec.volume.kind,
176
+ maxItems: eventData.spec.volume.maxitems,
177
+ status: "available",
178
+ tenant: tenantId,
179
+ };
180
+ };
181
+ /**
182
+ * Handles resource events (domain, port, secret, certificate, ca, volume)
183
+ * from WebSocket messages
184
+ */
185
+ export const handleResourceEvent = ({
186
+ kind,
187
+ eventData,
188
+ parentParts,
189
+ tenantsMap,
190
+ secretsMap,
191
+ hasRequestingServices,
192
+ }: HandleResourceEventParams): HandleResourceEventResult => {
193
+ const tenantId = parentParts.tenant;
194
+ let resource: Resource;
195
+ let secretsToStore: Array<{ key: string; value: any }> = [];
196
+ switch (kind) {
197
+ case "domain":
198
+ resource = buildDomainResource(eventData, tenantId, hasRequestingServices);
199
+ break;
200
+ case "port":
201
+ resource = buildPortResource(eventData, tenantId, hasRequestingServices);
202
+ break;
203
+ case "secret":
204
+ const secretResult = buildSecretResource(
205
+ eventData,
206
+ tenantId,
207
+ secretsMap
208
+ );
209
+ resource = secretResult.resource;
210
+ secretsToStore = secretResult.secretsToStore;
211
+ break;
212
+ case "certificate":
213
+ resource = buildCertificateResource(eventData, tenantId);
214
+ break;
215
+ case "ca":
216
+ resource = buildCAResource(eventData, tenantId);
217
+ break;
218
+ case "volume":
219
+ resource = buildVolumeResource(eventData, tenantId);
220
+ break;
221
+ default:
222
+ throw new Error(`Unknown resource kind: ${kind}`);
223
+ }
224
+ const tenant = tenantsMap.get(tenantId);
225
+ let tenantFound = false;
226
+ let updatedTenant: Tenant | null = null;
227
+ let pendingResource: Resource | null = null;
228
+ let publishUpdate = false;
229
+ let existingResource: Resource | null = null;
230
+
231
+ if (tenant) {
232
+ tenantFound = true;
233
+ const existingIndex = tenant.resources.findIndex(
234
+ (res) => res.name === resource.name && res.type === resource.type
235
+ );
236
+ if (existingIndex !== -1) {
237
+ existingResource = tenant.resources[existingIndex];
238
+ publishUpdate = true;
239
+ tenant.resources[existingIndex] = resource;
240
+ } else {
241
+ tenant.resources.push(resource);
242
+ }
243
+ updatedTenant = tenant;
244
+ } else {
245
+ pendingResource = resource;
246
+ console.warn(
247
+ `Recurso huérfano (${kind}) para tenant ${tenantId}, lo guardo en pendingDomains.`
248
+ );
249
+ }
250
+
251
+ return {
252
+ resource,
253
+ tenantId,
254
+ tenantFound,
255
+ updatedTenant,
256
+ pendingResource,
257
+ publishUpdate,
258
+ existingResource,
259
+ secretsToStore,
260
+ };
261
+ };
262
+
263
+ /**
264
+ * Handle domain event
265
+ */
266
+ export const handleDomainEvent = (
267
+ eventData: any,
268
+ parentParts: { [entity: string]: string },
269
+ tenantsMap: Map<string, Tenant>,
270
+ hasRequestingServices: boolean
271
+ ): HandleResourceEventResult => {
272
+ return handleResourceEvent({
273
+ kind: "domain",
274
+ eventData,
275
+ parentParts,
276
+ tenantsMap,
277
+ secretsMap: new Map(),
278
+ hasRequestingServices,
279
+ });
280
+ };
281
+
282
+ /**
283
+ * Handle port event
284
+ */
285
+ export const handlePortEvent = (
286
+ eventData: any,
287
+ parentParts: { [entity: string]: string },
288
+ tenantsMap: Map<string, Tenant>,
289
+ hasRequestingServices: boolean
290
+ ): HandleResourceEventResult => {
291
+ return handleResourceEvent({
292
+ kind: "port",
293
+ eventData,
294
+ parentParts,
295
+ tenantsMap,
296
+ secretsMap: new Map(),
297
+ hasRequestingServices
298
+ });
299
+ };
300
+
301
+ /**
302
+ * Handle secret event
303
+ */
304
+ export const handleSecretEvent = (
305
+ eventData: any,
306
+ parentParts: { [entity: string]: string },
307
+ tenantsMap: Map<string, Tenant>,
308
+ secretsMap: Map<string, any>
309
+ ): HandleResourceEventResult => {
310
+ return handleResourceEvent({
311
+ kind: "secret",
312
+ eventData,
313
+ parentParts,
314
+ tenantsMap,
315
+ secretsMap,
316
+ hasRequestingServices: false
317
+ });
318
+ };
319
+
320
+ /**
321
+ * Handle certificate event
322
+ */
323
+ export const handleCertificateEvent = (
324
+ eventData: any,
325
+ parentParts: { [entity: string]: string },
326
+ tenantsMap: Map<string, Tenant>
327
+ ): HandleResourceEventResult => {
328
+ return handleResourceEvent({
329
+ kind: "certificate",
330
+ eventData,
331
+ parentParts,
332
+ tenantsMap,
333
+ secretsMap: new Map(),
334
+ hasRequestingServices: false
335
+ });
336
+ };
337
+
338
+ /**
339
+ * Handle CA event
340
+ */
341
+ export const handleCAEvent = (
342
+ eventData: any,
343
+ parentParts: { [entity: string]: string },
344
+ tenantsMap: Map<string, Tenant>
345
+ ): HandleResourceEventResult => {
346
+ return handleResourceEvent({
347
+ kind: "ca",
348
+ eventData,
349
+ parentParts,
350
+ tenantsMap,
351
+ secretsMap: new Map(),
352
+ hasRequestingServices: false
353
+ });
354
+ };
355
+
356
+ /**
357
+ * Handle volume event
358
+ */
359
+ export const handleVolumeEvent = (
360
+ eventData: any,
361
+ parentParts: { [entity: string]: string },
362
+ tenantsMap: Map<string, Tenant>
363
+ ): HandleResourceEventResult => {
364
+ return handleResourceEvent({
365
+ kind: "volume",
366
+ eventData,
367
+ parentParts,
368
+ tenantsMap,
369
+ secretsMap: new Map(),
370
+ hasRequestingServices: false
371
+ });
372
+ };
373
+ export const processResourceResult = (
374
+ result: any,
375
+ tenantsMap: Map<string, any>,
376
+ pendingDomains: any[],
377
+ eventHelper: any,
378
+ ) => {
379
+ if (result.tenantFound && result.updatedTenant) {
380
+ if (result.publishUpdate && result.existingResource) {
381
+ eventHelper.resource.publish.updated(result.existingResource);
382
+ }
383
+ tenantsMap.set(result.tenantId, result.updatedTenant);
384
+ } else if (result.pendingResource) {
385
+ pendingDomains.push(result.pendingResource);
386
+ }
387
+ };