rhdh-e2e-test-utils 1.1.7 → 1.1.9
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/dist/deployment/keycloak/deployment.js +3 -3
- package/dist/deployment/rhdh/deployment.js +2 -2
- package/dist/playwright/base-config.js +1 -1
- package/dist/utils/kubernetes-client.d.ts +11 -2
- package/dist/utils/kubernetes-client.d.ts.map +1 -1
- package/dist/utils/kubernetes-client.js +88 -8
- package/dist/utils/plugin-metadata.d.ts +1 -1
- package/dist/utils/plugin-metadata.d.ts.map +1 -1
- package/dist/utils/plugin-metadata.js +62 -57
- package/package.json +1 -1
|
@@ -293,7 +293,7 @@ export class KeycloakHelper {
|
|
|
293
293
|
/**
|
|
294
294
|
* Wait for Keycloak to be ready
|
|
295
295
|
*/
|
|
296
|
-
async waitUntilReady(timeout =
|
|
296
|
+
async waitUntilReady(timeout = 500) {
|
|
297
297
|
this._log(`Waiting for Keycloak to be ready...`);
|
|
298
298
|
const labelSelector = `app.kubernetes.io/instance=${this.deploymentConfig.releaseName}`;
|
|
299
299
|
await this.k8sClient.waitForPodsWithFailureDetection(this.deploymentConfig.namespace, labelSelector, timeout);
|
|
@@ -343,14 +343,14 @@ spec:
|
|
|
343
343
|
}
|
|
344
344
|
async _waitForKeycloak() {
|
|
345
345
|
this._log("Waiting for Keycloak API to be ready...");
|
|
346
|
-
const timeout =
|
|
346
|
+
const timeout = 500;
|
|
347
347
|
const startTime = Date.now();
|
|
348
348
|
while (true) {
|
|
349
349
|
if (await this.isRunning()) {
|
|
350
350
|
break;
|
|
351
351
|
}
|
|
352
352
|
if ((Date.now() - startTime) / 1000 >= timeout) {
|
|
353
|
-
throw new Error(
|
|
353
|
+
throw new Error(`Keycloak API not ready after ${timeout} seconds`);
|
|
354
354
|
}
|
|
355
355
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
356
356
|
this._log(" Waiting for Keycloak API to be ready...");
|
|
@@ -21,7 +21,7 @@ export class RHDHDeployment {
|
|
|
21
21
|
}
|
|
22
22
|
async deploy() {
|
|
23
23
|
this._log("Starting RHDH deployment...");
|
|
24
|
-
test.setTimeout(
|
|
24
|
+
test.setTimeout(600_000);
|
|
25
25
|
await this.k8sClient.createNamespaceIfNotExists(this.deploymentConfig.namespace);
|
|
26
26
|
await this._applyAppConfig();
|
|
27
27
|
await this._applySecrets();
|
|
@@ -173,7 +173,7 @@ export class RHDHDeployment {
|
|
|
173
173
|
await $ `oc wait --for=delete pod -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub),app.kubernetes.io/name!=postgresql' -n ${namespace} --timeout=120s || true`;
|
|
174
174
|
await $ `oc scale deployment -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' --replicas=1 -n ${namespace}`;
|
|
175
175
|
}
|
|
176
|
-
async waitUntilReady(timeout =
|
|
176
|
+
async waitUntilReady(timeout = 500) {
|
|
177
177
|
this._log(`Waiting for RHDH deployment to be ready in namespace ${this.deploymentConfig.namespace}...`);
|
|
178
178
|
const labelSelector = "app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)";
|
|
179
179
|
try {
|
|
@@ -7,7 +7,7 @@ import { resolve } from "path";
|
|
|
7
7
|
export const baseConfig = {
|
|
8
8
|
testDir: "./tests",
|
|
9
9
|
forbidOnly: !!process.env.CI,
|
|
10
|
-
retries: process.env.CI ?
|
|
10
|
+
retries: process.env.CI ? 1 : 0,
|
|
11
11
|
workers: "50%",
|
|
12
12
|
outputDir: "node_modules/.cache/e2e-test-results",
|
|
13
13
|
timeout: 90_000,
|
|
@@ -37,9 +37,18 @@ declare class KubernetesClientHelper {
|
|
|
37
37
|
stringData?: Record<string, string>;
|
|
38
38
|
}, namespace: string): Promise<void>;
|
|
39
39
|
/**
|
|
40
|
-
* Delete a namespace
|
|
40
|
+
* Delete a namespace and wait for it to be fully terminated
|
|
41
41
|
*/
|
|
42
|
-
deleteNamespace(namespace: string): Promise<void>;
|
|
42
|
+
deleteNamespace(namespace: string, waitForDeletion?: boolean, timeoutSeconds?: number): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Wait for a namespace to be fully deleted
|
|
45
|
+
*/
|
|
46
|
+
private _waitForNamespaceDeletion;
|
|
47
|
+
/**
|
|
48
|
+
* Check if an error is a "not found" (404) error.
|
|
49
|
+
* Handles different error formats from various k8s client versions.
|
|
50
|
+
*/
|
|
51
|
+
private _isNotFoundError;
|
|
43
52
|
/**
|
|
44
53
|
* Check if a StatefulSet is ready (all replicas are available)
|
|
45
54
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kubernetes-client.d.ts","sourceRoot":"","sources":["../../src/utils/kubernetes-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,yBAAyB,CAAC;AAO/C;;GAEG;AACH,cAAM,sBAAsB;IAC1B,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,iBAAiB,CAAuB;;IAoChD;;OAEG;IACG,uBAAuB,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IA8C3B;;OAEG;IACG,0BAA0B,CAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IA+B3B;;UAEM;IAmBN;;OAEG;IAsBH;;OAEG;YACW,YAAY;IA8B1B;;OAEG;IACG,wBAAwB,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IA0ChB;;OAEG;IACG,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EAC7C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAqChB;;OAEG;IACG,eAAe,
|
|
1
|
+
{"version":3,"file":"kubernetes-client.d.ts","sourceRoot":"","sources":["../../src/utils/kubernetes-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,yBAAyB,CAAC;AAO/C;;GAEG;AACH,cAAM,sBAAsB;IAC1B,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,iBAAiB,CAAuB;;IAoChD;;OAEG;IACG,uBAAuB,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IA8C3B;;OAEG;IACG,0BAA0B,CAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IA+B3B;;UAEM;IAmBN;;OAEG;IAsBH;;OAEG;YACW,YAAY;IA8B1B;;OAEG;IACG,wBAAwB,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IA0ChB;;OAEG;IACG,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EAC7C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAqChB;;OAEG;IACG,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,eAAe,GAAE,OAAc,EAC/B,cAAc,GAAE,MAAY,GAC3B,OAAO,CAAC,IAAI,CAAC;IAyBhB;;OAEG;YACW,yBAAyB;IAsCvC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA2BxB;;OAEG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAc3E;;OAEG;IACG,uBAAuB,CAC3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,cAAc,GAAE,MAAY,EAC5B,cAAc,GAAE,MAAa,GAC5B,OAAO,CAAC,OAAO,CAAC;IAiBnB;;;OAGG;IACG,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;IAsBhD;;;;;;OAMG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBxE;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAmBxB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAQnC;IAEH;;;;;;;;OAQG;IACG,+BAA+B,CACnC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,cAAc,GAAE,MAAY,EAC5B,cAAc,GAAE,MAAa,GAC5B,OAAO,CAAC,IAAI,CAAC;IA8FhB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA8BzB;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -265,26 +265,80 @@ class KubernetesClientHelper {
|
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
/**
|
|
268
|
-
* Delete a namespace
|
|
268
|
+
* Delete a namespace and wait for it to be fully terminated
|
|
269
269
|
*/
|
|
270
|
-
async deleteNamespace(namespace) {
|
|
270
|
+
async deleteNamespace(namespace, waitForDeletion = true, timeoutSeconds = 180) {
|
|
271
271
|
try {
|
|
272
272
|
await this._k8sApi.deleteNamespace({ name: namespace });
|
|
273
|
-
console.log(
|
|
273
|
+
console.log(`[K8sHelper] Deleting namespace ${namespace}...`);
|
|
274
274
|
}
|
|
275
275
|
catch (error) {
|
|
276
276
|
// Ignore if namespace doesn't exist (already deleted), but throw other errors
|
|
277
|
-
|
|
278
|
-
if (err.body?.code === 404 ||
|
|
279
|
-
err.response?.statusCode === 404 ||
|
|
280
|
-
err.statusCode === 404) {
|
|
277
|
+
if (this._isNotFoundError(error)) {
|
|
281
278
|
console.log(`✓ Namespace ${namespace} already deleted or doesn't exist`);
|
|
279
|
+
return;
|
|
282
280
|
}
|
|
283
281
|
else {
|
|
284
282
|
console.error(`✗ Failed to delete namespace ${namespace}:`, error instanceof Error ? error.message : error);
|
|
285
283
|
throw error;
|
|
286
284
|
}
|
|
287
285
|
}
|
|
286
|
+
if (waitForDeletion) {
|
|
287
|
+
await this._waitForNamespaceDeletion(namespace, timeoutSeconds);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Wait for a namespace to be fully deleted
|
|
292
|
+
*/
|
|
293
|
+
async _waitForNamespaceDeletion(namespace, timeoutSeconds = 180) {
|
|
294
|
+
const startTime = Date.now();
|
|
295
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
296
|
+
const pollIntervalMs = 3000;
|
|
297
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
298
|
+
try {
|
|
299
|
+
const ns = await this._k8sApi.readNamespace({ name: namespace });
|
|
300
|
+
const phase = ns.status?.phase;
|
|
301
|
+
// Namespace still exists, wait and retry
|
|
302
|
+
if (phase === "Terminating") {
|
|
303
|
+
// Only log occasionally to avoid spam
|
|
304
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
305
|
+
if (elapsed % 10 === 0) {
|
|
306
|
+
console.log(`[K8sHelper] Namespace ${namespace} still terminating (${elapsed}s)...`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
// Check for 404 in various error formats from different k8s client versions
|
|
313
|
+
if (this._isNotFoundError(error)) {
|
|
314
|
+
console.log(`✓ Namespace ${namespace} fully deleted`);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
throw new Error(`Timeout waiting for namespace ${namespace} to be deleted after ${timeoutSeconds}s`);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Check if an error is a "not found" (404) error.
|
|
324
|
+
* Handles different error formats from various k8s client versions.
|
|
325
|
+
*/
|
|
326
|
+
_isNotFoundError(error) {
|
|
327
|
+
if (!error)
|
|
328
|
+
return false;
|
|
329
|
+
// Check error message for "404" or "not found"
|
|
330
|
+
if (error instanceof Error) {
|
|
331
|
+
const msg = error.message.toLowerCase();
|
|
332
|
+
if (msg.includes("404") || msg.includes("not found")) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Check various object properties for 404 status codes
|
|
337
|
+
const err = error;
|
|
338
|
+
return (err.body?.code === 404 ||
|
|
339
|
+
err.response?.statusCode === 404 ||
|
|
340
|
+
err.statusCode === 404 ||
|
|
341
|
+
err.code === 404);
|
|
288
342
|
}
|
|
289
343
|
/**
|
|
290
344
|
* Check if a StatefulSet is ready (all replicas are available)
|
|
@@ -397,7 +451,7 @@ class KubernetesClientHelper {
|
|
|
397
451
|
* @param timeoutSeconds - Maximum time to wait (default: 300)
|
|
398
452
|
* @param pollIntervalMs - How often to check pod status (default: 5000)
|
|
399
453
|
*/
|
|
400
|
-
async waitForPodsWithFailureDetection(namespace, labelSelector, timeoutSeconds =
|
|
454
|
+
async waitForPodsWithFailureDetection(namespace, labelSelector, timeoutSeconds = 500, pollIntervalMs = 5000) {
|
|
401
455
|
const startTime = Date.now();
|
|
402
456
|
const timeoutMs = timeoutSeconds * 1000;
|
|
403
457
|
console.log(`[K8sHelper] Waiting for pods (${labelSelector}) in ${namespace}...`);
|
|
@@ -443,8 +497,34 @@ class KubernetesClientHelper {
|
|
|
443
497
|
console.log(`[K8sHelper] All ${pods.length} pod(s) ready in ${namespace}`);
|
|
444
498
|
return;
|
|
445
499
|
}
|
|
500
|
+
// Log pod status every 20 seconds
|
|
501
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1000);
|
|
502
|
+
if (elapsedSec > 0 && elapsedSec % 20 === 0) {
|
|
503
|
+
try {
|
|
504
|
+
await $ `oc get pods -n ${namespace} -l ${labelSelector}`;
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
// Ignore errors
|
|
508
|
+
}
|
|
509
|
+
}
|
|
446
510
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
447
511
|
}
|
|
512
|
+
// Timeout reached - collect diagnostic info before throwing
|
|
513
|
+
console.log(`\n[K8sHelper] ═══ Pod Diagnostics (timeout reached) ═══`);
|
|
514
|
+
try {
|
|
515
|
+
console.log(`\n[K8sHelper] ─── Pod Status ───`);
|
|
516
|
+
await $ `oc get pods -n ${namespace} -l ${labelSelector} -o wide`;
|
|
517
|
+
console.log(`\n[K8sHelper] ─── Pod Details ───`);
|
|
518
|
+
await $ `oc describe pods -n ${namespace} -l ${labelSelector}`;
|
|
519
|
+
console.log(`\n[K8sHelper] ─── Namespace Events ───`);
|
|
520
|
+
await $ `oc get events -n ${namespace} --sort-by='.lastTimestamp'`;
|
|
521
|
+
console.log(`\n[K8sHelper] ─── Pod Logs ───`);
|
|
522
|
+
await $ `oc logs -n ${namespace} -l ${labelSelector} --all-containers --tail=100 2>&1 || true`;
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
// Ignore errors from diagnostic commands
|
|
526
|
+
}
|
|
527
|
+
console.log(`\n[K8sHelper] ═══ End Pod Diagnostics ═══\n`);
|
|
448
528
|
throw new Error(`Timeout waiting for pods (${labelSelector}) after ${timeoutSeconds}s`);
|
|
449
529
|
}
|
|
450
530
|
/**
|
|
@@ -52,7 +52,7 @@ export declare function getMetadataDirectory(metadataPath?: string): string | nu
|
|
|
52
52
|
* @param filePath Path to the metadata YAML file
|
|
53
53
|
* @returns PluginMetadata if valid, null otherwise
|
|
54
54
|
*/
|
|
55
|
-
export declare function parseMetadataFile(filePath: string): Promise<PluginMetadata
|
|
55
|
+
export declare function parseMetadataFile(filePath: string): Promise<PluginMetadata>;
|
|
56
56
|
/**
|
|
57
57
|
* Parses all metadata files in a directory and builds a map of plugin name to config.
|
|
58
58
|
* The plugin name is extracted from the dynamicArtifact path for flexible matching.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/plugin-metadata.ts"],"names":[],"mappings":"AAuHA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAmBpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,GAAE,MAA8B,GAC3C,MAAM,GAAG,IAAI,CAUf;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,
|
|
1
|
+
{"version":3,"file":"plugin-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/plugin-metadata.ts"],"names":[],"mappings":"AAuHA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAmBpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,GAAE,MAA8B,GAC3C,MAAM,GAAG,IAAI,CAUf;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CA0BzB;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAwBtC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AA4CD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,oBAAoB,EAAE,oBAAoB,EAC1C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GACvC,oBAAoB,CAsCtB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,wCAAwC,CAC5D,YAAY,GAAE,MAA8B,GAC3C,OAAO,CAAC,oBAAoB,CAAC,CAsD/B;AAED;;;;;;;;GAQG;AACH,wBAAsB,2BAA2B,CAC/C,oBAAoB,EAAE,oBAAoB,EAC1C,YAAY,GAAE,MAA8B,GAC3C,OAAO,CAAC,oBAAoB,CAAC,CAuC/B"}
|
|
@@ -142,28 +142,23 @@ export function getMetadataDirectory(metadataPath = DEFAULT_METADATA_PATH) {
|
|
|
142
142
|
* @returns PluginMetadata if valid, null otherwise
|
|
143
143
|
*/
|
|
144
144
|
export async function parseMetadataFile(filePath) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
console.log(`[PluginMetadata] Skipping ${filePath}: no spec.dynamicArtifact`);
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
console.log(`[PluginMetadata] Loaded metadata for: ${packagePath}`);
|
|
156
|
-
return {
|
|
157
|
-
packagePath,
|
|
158
|
-
pluginConfig: pluginConfig || {},
|
|
159
|
-
packageName: packageName || "",
|
|
160
|
-
sourceFile: filePath,
|
|
161
|
-
};
|
|
145
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
146
|
+
const parsed = yaml.load(content);
|
|
147
|
+
const packagePath = parsed?.spec?.dynamicArtifact;
|
|
148
|
+
const packageName = parsed?.spec?.packageName;
|
|
149
|
+
const pluginConfig = parsed?.spec?.appConfigExamples?.[0]?.content;
|
|
150
|
+
if (!packagePath) {
|
|
151
|
+
throw new Error(`[PluginMetadata] Missing required field spec.dynamicArtifact in ${filePath}`);
|
|
162
152
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return null;
|
|
153
|
+
if (!packageName) {
|
|
154
|
+
throw new Error(`[PluginMetadata] Missing required field spec.packageName in ${filePath}`);
|
|
166
155
|
}
|
|
156
|
+
return {
|
|
157
|
+
packagePath,
|
|
158
|
+
pluginConfig: pluginConfig || {},
|
|
159
|
+
packageName,
|
|
160
|
+
sourceFile: filePath,
|
|
161
|
+
};
|
|
167
162
|
}
|
|
168
163
|
/**
|
|
169
164
|
* Parses all metadata files in a directory and builds a map of plugin name to config.
|
|
@@ -179,16 +174,46 @@ export async function parseAllMetadataFiles(metadataDir) {
|
|
|
179
174
|
const metadataMap = new Map();
|
|
180
175
|
for (const file of files) {
|
|
181
176
|
const metadata = await parseMetadataFile(file);
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
metadataMap.set(pluginName, metadata);
|
|
186
|
-
console.log(`[PluginMetadata] Mapped plugin: ${pluginName} <- ${metadata.packagePath}`);
|
|
187
|
-
}
|
|
177
|
+
const pluginName = extractPluginName(metadata.packagePath);
|
|
178
|
+
metadataMap.set(pluginName, metadata);
|
|
179
|
+
console.log(`[PluginMetadata] Mapped plugin: ${pluginName} <- ${metadata.packagePath}`);
|
|
188
180
|
}
|
|
189
181
|
console.log(`[PluginMetadata] Successfully parsed ${metadataMap.size} plugin metadata entries`);
|
|
190
182
|
return metadataMap;
|
|
191
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Replaces local package paths with OCI URLs for plugins that have matching metadata.
|
|
186
|
+
* Only applies to plugins with metadata (workspace plugins). Plugins without metadata
|
|
187
|
+
* (e.g., keycloak, bulk-import from auth defaults) keep their original paths.
|
|
188
|
+
*
|
|
189
|
+
* @param plugins The plugin entries to process
|
|
190
|
+
* @param metadataMap Map of plugin names to plugin metadata
|
|
191
|
+
* @param metadataPath Path to the metadata directory (used to resolve workspace root)
|
|
192
|
+
* @returns Plugin entries with OCI URLs replaced where applicable
|
|
193
|
+
*/
|
|
194
|
+
async function replaceWithOCIUrls(plugins, metadataMap, metadataPath) {
|
|
195
|
+
const prNumber = process.env.GIT_PR_NUMBER;
|
|
196
|
+
if (!prNumber) {
|
|
197
|
+
return plugins;
|
|
198
|
+
}
|
|
199
|
+
console.log(`[PluginMetadata] PR build detected (PR #${prNumber}), fetching OCI URLs...`);
|
|
200
|
+
const workspacePath = path.resolve(metadataPath, "..");
|
|
201
|
+
const ociUrls = await getOCIUrlsForPR(workspacePath, prNumber);
|
|
202
|
+
return plugins.map((plugin) => {
|
|
203
|
+
const pluginName = extractPluginName(plugin.package);
|
|
204
|
+
const metadata = metadataMap.get(pluginName);
|
|
205
|
+
if (!metadata?.packageName)
|
|
206
|
+
return plugin;
|
|
207
|
+
const displayName = metadata.packageName
|
|
208
|
+
.replace(/^@/, "")
|
|
209
|
+
.replace(/\//g, "-");
|
|
210
|
+
const ociUrl = ociUrls.get(displayName);
|
|
211
|
+
if (!ociUrl)
|
|
212
|
+
return plugin;
|
|
213
|
+
console.log(`[PluginMetadata] Replacing ${plugin.package} with ${ociUrl}`);
|
|
214
|
+
return { ...plugin, package: ociUrl };
|
|
215
|
+
});
|
|
216
|
+
}
|
|
192
217
|
/**
|
|
193
218
|
* Injects plugin configurations from metadata into a dynamic plugins config.
|
|
194
219
|
* Metadata config serves as the base, user-provided pluginConfig overrides it.
|
|
@@ -255,43 +280,18 @@ export async function generateDynamicPluginsConfigFromMetadata(metadataPath = DE
|
|
|
255
280
|
if (metadataMap.size === 0) {
|
|
256
281
|
throw new Error(`[PluginMetadata] Cannot generate dynamic-plugins config: no valid metadata files found in ${metadataDir}`);
|
|
257
282
|
}
|
|
258
|
-
// If PR number is set, fetch OCI URLs
|
|
259
|
-
const prNumber = process.env.GIT_PR_NUMBER;
|
|
260
|
-
let ociUrls = null;
|
|
261
|
-
if (prNumber) {
|
|
262
|
-
console.log(`[PluginMetadata] PR build detected (PR #${prNumber}), fetching OCI URLs...`);
|
|
263
|
-
const workspacePath = path.resolve(metadataPath, "..");
|
|
264
|
-
ociUrls = await getOCIUrlsForPR(workspacePath, prNumber);
|
|
265
|
-
}
|
|
266
283
|
// Build plugin entries from metadata
|
|
267
|
-
|
|
284
|
+
let plugins = [];
|
|
268
285
|
for (const [pluginName, metadata] of metadataMap) {
|
|
269
|
-
|
|
270
|
-
// Replace with OCI URL if available (required for PR builds)
|
|
271
|
-
if (ociUrls) {
|
|
272
|
-
if (!metadata.packageName) {
|
|
273
|
-
throw new Error(`[PluginMetadata] PR build requires packageName in metadata but not found for: ${pluginName}\n` +
|
|
274
|
-
` Source file: ${metadata.sourceFile}`);
|
|
275
|
-
}
|
|
276
|
-
const displayName = metadata.packageName
|
|
277
|
-
.replace(/^@/, "")
|
|
278
|
-
.replace(/\//g, "-");
|
|
279
|
-
const ociUrl = ociUrls.get(displayName);
|
|
280
|
-
if (!ociUrl) {
|
|
281
|
-
throw new Error(`[PluginMetadata] PR build requires OCI URL but not found for: ${displayName}\n` +
|
|
282
|
-
` Package name: ${metadata.packageName}\n` +
|
|
283
|
-
` Source file: ${metadata.sourceFile}`);
|
|
284
|
-
}
|
|
285
|
-
console.log(`[PluginMetadata] Replacing ${packageRef} with ${ociUrl}`);
|
|
286
|
-
packageRef = ociUrl;
|
|
287
|
-
}
|
|
288
|
-
console.log(`[PluginMetadata] Adding plugin: ${pluginName} (${packageRef})`);
|
|
286
|
+
console.log(`[PluginMetadata] Adding plugin: ${pluginName} (${metadata.packagePath})`);
|
|
289
287
|
plugins.push({
|
|
290
|
-
package:
|
|
288
|
+
package: metadata.packagePath,
|
|
291
289
|
disabled: false,
|
|
292
290
|
pluginConfig: metadata.pluginConfig,
|
|
293
291
|
});
|
|
294
292
|
}
|
|
293
|
+
// Replace local paths with OCI URLs for PR builds
|
|
294
|
+
plugins = await replaceWithOCIUrls(plugins, metadataMap, metadataPath);
|
|
295
295
|
console.log(`[PluginMetadata] Generated dynamic-plugins config with ${plugins.length} plugins`);
|
|
296
296
|
return { plugins };
|
|
297
297
|
}
|
|
@@ -321,5 +321,10 @@ export async function loadAndInjectPluginMetadata(dynamicPluginsConfig, metadata
|
|
|
321
321
|
throw new Error(`[PluginMetadata] PR build requires plugin metadata but no valid metadata files found in ${metadataDir}`);
|
|
322
322
|
}
|
|
323
323
|
// Inject metadata configs into the dynamic plugins config
|
|
324
|
-
|
|
324
|
+
const result = injectMetadataConfig(dynamicPluginsConfig, metadataMap);
|
|
325
|
+
// Replace local paths with OCI URLs for PR builds
|
|
326
|
+
if (result.plugins) {
|
|
327
|
+
result.plugins = await replaceWithOCIUrls(result.plugins, metadataMap, metadataPath);
|
|
328
|
+
}
|
|
329
|
+
return result;
|
|
325
330
|
}
|