@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,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
|
+
};
|