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.
@@ -293,7 +293,7 @@ export class KeycloakHelper {
293
293
  /**
294
294
  * Wait for Keycloak to be ready
295
295
  */
296
- async waitUntilReady(timeout = 300) {
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 = 300;
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("Keycloak API not ready after 5 minutes");
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(500_000);
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 = 300) {
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 ? 2 : 0,
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,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BvD;;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;IAiEhB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA8BzB;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
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(`✓ Deleted namespace ${namespace}`);
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
- const err = error;
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 = 300, pollIntervalMs = 5000) {
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 | null>;
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,GAAG,IAAI,CAAC,CA4BhC;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CA2BtC;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;AAED;;;;;;;;;;;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,CA0F/B;AAED;;;;;;;;GAQG;AACH,wBAAsB,2BAA2B,CAC/C,oBAAoB,EAAE,oBAAoB,EAC1C,YAAY,GAAE,MAA8B,GAC3C,OAAO,CAAC,oBAAoB,CAAC,CA4B/B"}
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
- try {
146
- const content = await fs.readFile(filePath, "utf8");
147
- const parsed = yaml.load(content);
148
- const packagePath = parsed?.spec?.dynamicArtifact;
149
- const packageName = parsed?.spec?.packageName;
150
- const pluginConfig = parsed?.spec?.appConfigExamples?.[0]?.content;
151
- if (!packagePath) {
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
- catch (error) {
164
- console.error(`[PluginMetadata] Error parsing ${filePath}:`, error);
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
- if (metadata) {
183
- // Use extracted plugin name as key for flexible matching
184
- const pluginName = extractPluginName(metadata.packagePath);
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
- const plugins = [];
284
+ let plugins = [];
268
285
  for (const [pluginName, metadata] of metadataMap) {
269
- let packageRef = metadata.packagePath;
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: packageRef,
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
- return injectMetadataConfig(dynamicPluginsConfig, metadataMap);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhdh-e2e-test-utils",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "Test utilities for RHDH E2E tests",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",