rhdh-e2e-test-utils 1.1.4 → 1.1.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../../src/deployment/keycloak/deployment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAY1E,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IAClB,SAAS,yBAAgC;IACzC,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,WAAW,EAAE,MAAM,CAAM;IACzB,KAAK,EAAE,MAAM,CAAM;IACnB,QAAQ,EAAE,MAAM,CAAM;IACtB,YAAY,EAAE,MAAM,CAAM;IACjC,OAAO,CAAC,YAAY,CAAoC;gBAE5C,OAAO,GAAE,yBAA8B;IAInD;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAa7B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAUnC;;OAEG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACvC,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;QAC/B,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAC;KAC9B,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCjB;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB9D;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB7D;;OAEG;IACG,YAAY,CAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IAqChB;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5E;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8C1E;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAe5D;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAQ9D;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhE;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlE;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/C;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B;;OAEG;IACG,cAAc,CAAC,OAAO,GAAE,MAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1D,OAAO,CAAC,sBAAsB;YAahB,eAAe;YAWf,YAAY;IAwBpB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;YAO3B,gBAAgB;YAoBhB,sBAAsB;YActB,kBAAkB;YAQlB,0BAA0B;YAmD1B,eAAe;IAqB7B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,IAAI;CAGb"}
1
+ {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../../src/deployment/keycloak/deployment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAY1E,OAAO,KAAK,EACV,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IAClB,SAAS,yBAAgC;IACzC,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,WAAW,EAAE,MAAM,CAAM;IACzB,KAAK,EAAE,MAAM,CAAM;IACnB,QAAQ,EAAE,MAAM,CAAM;IACtB,YAAY,EAAE,MAAM,CAAM;IACjC,OAAO,CAAC,YAAY,CAAoC;gBAE5C,OAAO,GAAE,yBAA8B;IAInD;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAa7B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAUnC;;OAEG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACvC,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;QAC/B,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAC;KAC9B,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCjB;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB9D;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB7D;;OAEG;IACG,YAAY,CAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IAqChB;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5E;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8C1E;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAe5D;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAQ9D;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhE;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlE;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/C;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B;;OAEG;IACG,cAAc,CAAC,OAAO,GAAE,MAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1D,OAAO,CAAC,sBAAsB;YAahB,eAAe;YAWf,YAAY;IAwBpB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;YAO3B,gBAAgB;YAoBhB,sBAAsB;YActB,kBAAkB;YAQlB,0BAA0B;YAmD1B,eAAe;IAqB7B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,IAAI;CAGb"}
@@ -295,7 +295,8 @@ export class KeycloakHelper {
295
295
  */
296
296
  async waitUntilReady(timeout = 300) {
297
297
  this._log(`Waiting for Keycloak to be ready...`);
298
- await this.k8sClient.waitForStatefulSetReady(this.deploymentConfig.namespace, this.deploymentConfig.releaseName, timeout);
298
+ const labelSelector = `app.kubernetes.io/instance=${this.deploymentConfig.releaseName}`;
299
+ await this.k8sClient.waitForPodsWithFailureDetection(this.deploymentConfig.namespace, labelSelector, timeout);
299
300
  }
300
301
  // Private methods
301
302
  _buildDeploymentConfig(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../../src/deployment/rhdh/deployment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAiB1E,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IAClB,SAAS,yBAAgC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;gBAE9B,SAAS,EAAE,MAAM;IAUvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBf,eAAe;YAgBf,aAAa;IAe3B;;;OAGG;YACW,0BAA0B;YAuC1B,oBAAoB;YAWpB,eAAe;YAmCf,mBAAmB;IA4B3B,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAWrC;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOpC,cAAc,CAAC,OAAO,GAAE,MAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBpD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAIjB,oBAAoB;IA6BlC,OAAO,CAAC,sBAAsB;IAoCxB,SAAS,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,SAAS;CAmBlB"}
1
+ {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../../src/deployment/rhdh/deployment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAgB1E,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IAClB,SAAS,yBAAgC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;gBAE9B,SAAS,EAAE,MAAM;IAUvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBf,eAAe;YAgBf,aAAa;IAe3B;;;OAGG;YACW,0BAA0B;YAuC1B,oBAAoB;YAWpB,eAAe;YAmCf,mBAAmB;IA4B3B,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAWrC;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOpC,cAAc,CAAC,OAAO,GAAE,MAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBpD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAIjB,oBAAoB;IA6BlC,OAAO,CAAC,sBAAsB;IAoCxB,SAAS,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,SAAS;CAMlB"}
@@ -6,7 +6,6 @@ import { mergeYamlFilesIfExists, deepMerge } from "../../utils/merge-yamls.js";
6
6
  import { loadAndInjectPluginMetadata, generateDynamicPluginsConfigFromMetadata, } from "../../utils/plugin-metadata.js";
7
7
  import { envsubst } from "../../utils/common.js";
8
8
  import fs from "fs-extra";
9
- import boxen from "boxen";
10
9
  import { DEFAULT_CONFIG_PATHS, AUTH_CONFIG_PATHS, CHART_URL, } from "./constants.js";
11
10
  export class RHDHDeployment {
12
11
  k8sClient = new KubernetesClientHelper();
@@ -153,16 +152,13 @@ export class RHDHDeployment {
153
152
  }
154
153
  async waitUntilReady(timeout = 300) {
155
154
  this._log(`Waiting for RHDH deployment to be ready in namespace ${this.deploymentConfig.namespace}...`);
155
+ const labelSelector = "app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)";
156
156
  try {
157
- await $ `oc rollout status deployment -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' -n ${this.deploymentConfig.namespace} --timeout=${timeout}s`;
157
+ await this.k8sClient.waitForPodsWithFailureDetection(this.deploymentConfig.namespace, labelSelector, timeout);
158
158
  this._log(`RHDH deployment is ready in namespace ${this.deploymentConfig.namespace}`);
159
159
  }
160
160
  catch (error) {
161
- console.log("----------------------------------------------------------------");
162
- console.log("Deployment Failed Logs");
163
- console.log("----------------------------------------------------------------");
164
- await $ `oc logs -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' -n ${this.deploymentConfig.namespace} --tail=100`;
165
- throw new Error(`Error waiting for RHDH deployment to be ready in timeout ${timeout}s in namespace ${this.deploymentConfig.namespace}: ${error}`);
161
+ throw new Error(`RHDH deployment failed in namespace ${this.deploymentConfig.namespace}: ${error instanceof Error ? error.message : error}`);
166
162
  }
167
163
  }
168
164
  async teardown() {
@@ -243,20 +239,9 @@ export class RHDHDeployment {
243
239
  console.log("[RHDHDeployment]", ...args);
244
240
  }
245
241
  _logBoxen(title, data) {
246
- console.log(boxen(yaml.dump(data, { lineWidth: -1 }), {
247
- title,
248
- padding: 0,
249
- width: 120,
250
- borderStyle: {
251
- topLeft: "┌",
252
- topRight: "┐",
253
- bottomLeft: "└",
254
- bottomRight: "┘",
255
- top: "─",
256
- bottom: "─",
257
- left: "",
258
- right: "",
259
- },
260
- }));
242
+ const content = yaml.dump(data, { lineWidth: -1 });
243
+ console.log(`\n┌─ ${title} ${"─".repeat(60)}`);
244
+ console.log(content);
245
+ console.log(`└${"─".repeat(60 + title.length + 3)}\n`);
261
246
  }
262
247
  }
@@ -65,6 +65,24 @@ declare class KubernetesClientHelper {
65
65
  * Extract the URL from a route object
66
66
  */
67
67
  private _extractRouteUrl;
68
+ /**
69
+ * Failure states that indicate a pod will not recover without intervention
70
+ */
71
+ private static readonly failureReasons;
72
+ /**
73
+ * Wait for pods matching a label selector to be ready, with early failure detection.
74
+ * Fails fast when it detects unrecoverable states like CrashLoopBackOff.
75
+ *
76
+ * @param namespace - Namespace to watch
77
+ * @param labelSelector - Label selector (e.g., "app=myapp")
78
+ * @param timeoutSeconds - Maximum time to wait (default: 300)
79
+ * @param pollIntervalMs - How often to check pod status (default: 5000)
80
+ */
81
+ waitForPodsWithFailureDetection(namespace: string, labelSelector: string, timeoutSeconds?: number, pollIntervalMs?: number): Promise<void>;
82
+ /**
83
+ * Check if a pod is in a failure state. Returns failure info or null if healthy.
84
+ */
85
+ private _checkPodFailure;
68
86
  }
69
87
  export { KubernetesClientHelper };
70
88
  //# sourceMappingURL=kubernetes-client.d.ts.map
@@ -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;CAkBzB;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,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"}
@@ -376,5 +376,103 @@ class KubernetesClientHelper {
376
376
  const protocol = routeObj.spec?.tls ? "https" : "http";
377
377
  return `${protocol}://${host}`;
378
378
  }
379
+ /**
380
+ * Failure states that indicate a pod will not recover without intervention
381
+ */
382
+ static failureReasons = new Set([
383
+ "CrashLoopBackOff",
384
+ "Error",
385
+ "ImagePullBackOff",
386
+ "ErrImagePull",
387
+ "InvalidImageName",
388
+ "CreateContainerConfigError",
389
+ "CreateContainerError",
390
+ ]);
391
+ /**
392
+ * Wait for pods matching a label selector to be ready, with early failure detection.
393
+ * Fails fast when it detects unrecoverable states like CrashLoopBackOff.
394
+ *
395
+ * @param namespace - Namespace to watch
396
+ * @param labelSelector - Label selector (e.g., "app=myapp")
397
+ * @param timeoutSeconds - Maximum time to wait (default: 300)
398
+ * @param pollIntervalMs - How often to check pod status (default: 5000)
399
+ */
400
+ async waitForPodsWithFailureDetection(namespace, labelSelector, timeoutSeconds = 300, pollIntervalMs = 5000) {
401
+ const startTime = Date.now();
402
+ const timeoutMs = timeoutSeconds * 1000;
403
+ console.log(`[K8sHelper] Waiting for pods (${labelSelector}) in ${namespace}...`);
404
+ while (Date.now() - startTime < timeoutMs) {
405
+ let pods;
406
+ try {
407
+ pods = (await this._k8sApi.listNamespacedPod({ namespace, labelSelector })).items;
408
+ }
409
+ catch (err) {
410
+ console.log(`[K8sHelper] API error, retrying: ${err}`);
411
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
412
+ continue;
413
+ }
414
+ if (pods.length === 0) {
415
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
416
+ continue;
417
+ }
418
+ for (const pod of pods) {
419
+ const podName = pod.metadata?.name || "unknown";
420
+ const failure = this._checkPodFailure(pod);
421
+ if (failure) {
422
+ console.log(`[K8sHelper] Pod ${podName} failed: ${failure.reason}`);
423
+ try {
424
+ if (failure.container) {
425
+ await $ `oc logs ${podName} -n ${namespace} -c ${failure.container} --tail=100`;
426
+ }
427
+ else {
428
+ await $ `oc logs ${podName} -n ${namespace} --tail=100`;
429
+ }
430
+ }
431
+ catch {
432
+ // Ignore log fetch errors
433
+ }
434
+ throw new Error(`Pod ${podName} failed: ${failure.reason}`);
435
+ }
436
+ }
437
+ // Check if all pods are ready
438
+ const allReady = pods.every((pod) => {
439
+ const ready = pod.status?.conditions?.find((c) => c.type === "Ready");
440
+ return ready?.status === "True";
441
+ });
442
+ if (allReady) {
443
+ console.log(`[K8sHelper] All ${pods.length} pod(s) ready in ${namespace}`);
444
+ return;
445
+ }
446
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
447
+ }
448
+ throw new Error(`Timeout waiting for pods (${labelSelector}) after ${timeoutSeconds}s`);
449
+ }
450
+ /**
451
+ * Check if a pod is in a failure state. Returns failure info or null if healthy.
452
+ */
453
+ _checkPodFailure(pod) {
454
+ // Check init containers first
455
+ for (const cs of pod.status?.initContainerStatuses || []) {
456
+ const reason = cs.state?.waiting?.reason;
457
+ if (reason && KubernetesClientHelper.failureReasons.has(reason)) {
458
+ return { reason: `Init:${reason}`, container: cs.name };
459
+ }
460
+ if (cs.state?.terminated?.exitCode &&
461
+ cs.state.terminated.exitCode !== 0) {
462
+ return {
463
+ reason: `Init:Error (exit ${cs.state.terminated.exitCode})`,
464
+ container: cs.name,
465
+ };
466
+ }
467
+ }
468
+ // Check main containers
469
+ for (const cs of pod.status?.containerStatuses || []) {
470
+ const reason = cs.state?.waiting?.reason;
471
+ if (reason && KubernetesClientHelper.failureReasons.has(reason)) {
472
+ return { reason, container: cs.name };
473
+ }
474
+ }
475
+ return null;
476
+ }
379
477
  }
380
478
  export { KubernetesClientHelper };
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/plugin-metadata.ts"],"names":[],"mappings":"AAMA;;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,CAmChC;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,CAmD/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,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"}
@@ -3,6 +3,75 @@ import path from "path";
3
3
  import yaml from "js-yaml";
4
4
  import { glob } from "zx";
5
5
  import { deepMerge } from "./merge-yamls.js";
6
+ const OCI_REGISTRY_PREFIX = "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays";
7
+ /**
8
+ * Fetches plugin versions from source repo and builds OCI URL map.
9
+ * Only called when GIT_PR_NUMBER is set.
10
+ */
11
+ async function getOCIUrlsForPR(workspacePath, prNumber) {
12
+ const ociUrls = new Map();
13
+ const sourceJsonPath = path.join(workspacePath, "source.json");
14
+ const pluginsListPath = path.join(workspacePath, "plugins-list.yaml");
15
+ if (!fs.existsSync(sourceJsonPath)) {
16
+ throw new Error(`[PluginMetadata] PR build requires source.json but not found at: ${sourceJsonPath}`);
17
+ }
18
+ if (!fs.existsSync(pluginsListPath)) {
19
+ throw new Error(`[PluginMetadata] PR build requires plugins-list.yaml but not found at: ${pluginsListPath}`);
20
+ }
21
+ const sourceJson = JSON.parse(await fs.readFile(sourceJsonPath, "utf-8"));
22
+ const { repo, "repo-ref": ref, "repo-flat": repoFlat } = sourceJson;
23
+ if (!repo) {
24
+ throw new Error(`[PluginMetadata] source.json is missing required 'repo' field: ${sourceJsonPath}`);
25
+ }
26
+ if (!ref) {
27
+ throw new Error(`[PluginMetadata] source.json is missing required 'repo-ref' field: ${sourceJsonPath}`);
28
+ }
29
+ // Parse owner/repo from URL
30
+ const match = repo.match(/github\.com\/(.+?)(?:\.git)?$/);
31
+ if (!match) {
32
+ throw new Error(`[PluginMetadata] Failed to parse GitHub repo from source.json: ${repo}`);
33
+ }
34
+ const ownerRepo = match[1];
35
+ // Read plugins list
36
+ const pluginsList = await fs.readFile(pluginsListPath, "utf-8");
37
+ const pluginPaths = pluginsList
38
+ .trim()
39
+ .split("\n")
40
+ .map((l) => l.replace(/:$/, "").trim())
41
+ .filter(Boolean);
42
+ const workspaceName = path.basename(workspacePath);
43
+ console.log(`[PluginMetadata] Fetching versions for ${pluginPaths.length} plugins from source...`);
44
+ for (const pluginPath of pluginPaths) {
45
+ const pkgJsonPath = repoFlat
46
+ ? `${pluginPath}/package.json`
47
+ : `workspaces/${workspaceName}/${pluginPath}/package.json`;
48
+ const rawUrl = `https://raw.githubusercontent.com/${ownerRepo}/${ref}/${pkgJsonPath}`;
49
+ const res = await fetch(rawUrl);
50
+ if (!res.ok) {
51
+ throw new Error(`[PluginMetadata] Failed to fetch package.json for ${pluginPath}: ${res.status} ${res.statusText}\n` +
52
+ ` URL: ${rawUrl}`);
53
+ }
54
+ const pkgJson = (await res.json());
55
+ if (!pkgJson.name) {
56
+ throw new Error(`[PluginMetadata] package.json is missing 'name' field for ${pluginPath}\n` +
57
+ ` URL: ${rawUrl}`);
58
+ }
59
+ if (!pkgJson.version) {
60
+ throw new Error(`[PluginMetadata] package.json is missing 'version' field for ${pluginPath}\n` +
61
+ ` URL: ${rawUrl}`);
62
+ }
63
+ const { name, version } = pkgJson;
64
+ // @backstage-community/plugin-tech-radar -> backstage-community-plugin-tech-radar
65
+ const displayName = name.replace(/^@/, "").replace(/\//g, "-");
66
+ // TODO(RHDHBUGS-2530): Remove !alias suffix once Konflux builds include
67
+ // io.backstage.dynamic-packages annotation. The suffix is a workaround
68
+ // because install-dynamic-plugins.py can't auto-detect plugin path without it.
69
+ const ociUrl = `${OCI_REGISTRY_PREFIX}/${displayName}:pr_${prNumber}__${version}!${displayName}`;
70
+ ociUrls.set(displayName, ociUrl);
71
+ console.log(`[PluginMetadata] ${displayName} -> ${ociUrl}`);
72
+ }
73
+ return ociUrls;
74
+ }
6
75
  /**
7
76
  * Checks if plugin metadata handling should be enabled.
8
77
  * This controls both auto-generation and injection of plugin metadata.
@@ -83,14 +152,10 @@ export async function parseMetadataFile(filePath) {
83
152
  console.log(`[PluginMetadata] Skipping ${filePath}: no spec.dynamicArtifact`);
84
153
  return null;
85
154
  }
86
- if (!pluginConfig) {
87
- console.log(`[PluginMetadata] Skipping ${filePath}: no spec.appConfigExamples[0].content`);
88
- return null;
89
- }
90
155
  console.log(`[PluginMetadata] Loaded metadata for: ${packagePath}`);
91
156
  return {
92
157
  packagePath,
93
- pluginConfig,
158
+ pluginConfig: pluginConfig || {},
94
159
  packageName: packageName || "",
95
160
  sourceFile: filePath,
96
161
  };
@@ -190,12 +255,39 @@ export async function generateDynamicPluginsConfigFromMetadata(metadataPath = DE
190
255
  if (metadataMap.size === 0) {
191
256
  throw new Error(`[PluginMetadata] Cannot generate dynamic-plugins config: no valid metadata files found in ${metadataDir}`);
192
257
  }
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
+ }
193
266
  // Build plugin entries from metadata
194
267
  const plugins = [];
195
268
  for (const [pluginName, metadata] of metadataMap) {
196
- console.log(`[PluginMetadata] Adding plugin from metadata: ${pluginName} (${metadata.packagePath})`);
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})`);
197
289
  plugins.push({
198
- package: metadata.packagePath,
290
+ package: packageRef,
199
291
  disabled: false,
200
292
  pluginConfig: metadata.pluginConfig,
201
293
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhdh-e2e-test-utils",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Test utilities for RHDH E2E tests",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -82,7 +82,6 @@
82
82
  "@eslint/js": "^9.39.1",
83
83
  "@keycloak/keycloak-admin-client": "^26.0.0",
84
84
  "@kubernetes/client-node": "^1.4.0",
85
- "boxen": "^8.0.1",
86
85
  "eslint": "^9.39.1",
87
86
  "eslint-plugin-check-file": "^3.3.1",
88
87
  "eslint-plugin-playwright": "^2.4.0",