rhdh-e2e-test-utils 1.1.3 → 1.1.4

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.
@@ -8,6 +8,11 @@ export declare class RHDHDeployment {
8
8
  deploy(): Promise<void>;
9
9
  private _applyAppConfig;
10
10
  private _applySecrets;
11
+ /**
12
+ * Builds the merged dynamic plugins configuration.
13
+ * Merges: package defaults + auth config + user config + metadata (for PR builds).
14
+ */
15
+ private _buildDynamicPluginsConfig;
11
16
  private _applyDynamicPlugins;
12
17
  private _deployWithHelm;
13
18
  private _deployWithOperator;
@@ -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;AAa1E,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;YAeb,oBAAoB;YAkBpB,eAAe;YA2Cf,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;CAGlB"}
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"}
@@ -2,7 +2,8 @@ import { KubernetesClientHelper } from "../../utils/kubernetes-client.js";
2
2
  import { $ } from "../../utils/bash.js";
3
3
  import yaml from "js-yaml";
4
4
  import { test } from "@playwright/test";
5
- import { mergeYamlFilesIfExists } from "../../utils/merge-yamls.js";
5
+ import { mergeYamlFilesIfExists, deepMerge } from "../../utils/merge-yamls.js";
6
+ import { loadAndInjectPluginMetadata, generateDynamicPluginsConfigFromMetadata, } from "../../utils/plugin-metadata.js";
6
7
  import { envsubst } from "../../utils/common.js";
7
8
  import fs from "fs-extra";
8
9
  import boxen from "boxen";
@@ -53,13 +54,37 @@ export class RHDHDeployment {
53
54
  ]);
54
55
  await this.k8sClient.applySecretFromObject("rhdh-secrets", JSON.parse(envsubst(JSON.stringify(secretsYaml))), this.deploymentConfig.namespace);
55
56
  }
56
- async _applyDynamicPlugins() {
57
+ /**
58
+ * Builds the merged dynamic plugins configuration.
59
+ * Merges: package defaults + auth config + user config + metadata (for PR builds).
60
+ */
61
+ async _buildDynamicPluginsConfig() {
62
+ const userConfigPath = this.deploymentConfig.dynamicPlugins;
63
+ const userConfigExists = userConfigPath && fs.existsSync(userConfigPath);
57
64
  const authConfig = AUTH_CONFIG_PATHS[this.deploymentConfig.auth];
58
- const dynamicPluginsYaml = await mergeYamlFilesIfExists([
65
+ // If user's dynamic-plugins config doesn't exist, auto-generate from metadata
66
+ if (!userConfigExists) {
67
+ this._log(`Dynamic plugins config not found at '${userConfigPath}', auto-generating from metadata...`);
68
+ const metadataConfig = await generateDynamicPluginsConfigFromMetadata();
69
+ // Merge with package defaults and auth config
70
+ const authPlugins = await mergeYamlFilesIfExists([DEFAULT_CONFIG_PATHS.dynamicPlugins, authConfig.dynamicPlugins], { arrayMergeStrategy: { byKey: "package" } });
71
+ return deepMerge(metadataConfig, authPlugins, {
72
+ arrayMergeStrategy: { byKey: "package" },
73
+ });
74
+ }
75
+ // User config exists - merge provided configs and inject metadata for listed plugins only
76
+ let dynamicPluginsConfig = await mergeYamlFilesIfExists([
59
77
  DEFAULT_CONFIG_PATHS.dynamicPlugins,
60
78
  authConfig.dynamicPlugins,
61
- this.deploymentConfig.dynamicPlugins,
79
+ userConfigPath,
62
80
  ], { arrayMergeStrategy: { byKey: "package" } });
81
+ // Inject plugin metadata configuration for plugins in the config
82
+ dynamicPluginsConfig =
83
+ await loadAndInjectPluginMetadata(dynamicPluginsConfig);
84
+ return dynamicPluginsConfig;
85
+ }
86
+ async _applyDynamicPlugins() {
87
+ const dynamicPluginsYaml = await this._buildDynamicPluginsConfig();
63
88
  this._logBoxen("Dynamic Plugins", dynamicPluginsYaml);
64
89
  await this.k8sClient.applyConfigMapFromObject("dynamic-plugins", dynamicPluginsYaml, this.deploymentConfig.namespace);
65
90
  }
@@ -72,15 +97,10 @@ export class RHDHDeployment {
72
97
  ]));
73
98
  this._logBoxen("Value File", valueFileObject);
74
99
  // Merge dynamic plugins into the values file (including auth-specific plugins)
75
- const authConfig = AUTH_CONFIG_PATHS[this.deploymentConfig.auth];
76
100
  if (!valueFileObject.global) {
77
101
  valueFileObject.global = {};
78
102
  }
79
- valueFileObject.global.dynamic = await mergeYamlFilesIfExists([
80
- DEFAULT_CONFIG_PATHS.dynamicPlugins,
81
- authConfig.dynamicPlugins,
82
- this.deploymentConfig.dynamicPlugins,
83
- ], { arrayMergeStrategy: { byKey: "package" } });
103
+ valueFileObject.global.dynamic = await this._buildDynamicPluginsConfig();
84
104
  this._logBoxen("Dynamic Plugins", valueFileObject.global.dynamic);
85
105
  fs.writeFileSync(`/tmp/${this.deploymentConfig.namespace}-value-file.yaml`, yaml.dump(valueFileObject));
86
106
  await $ `
@@ -223,6 +243,20 @@ export class RHDHDeployment {
223
243
  console.log("[RHDHDeployment]", ...args);
224
244
  }
225
245
  _logBoxen(title, data) {
226
- console.log(boxen(yaml.dump(data), { title, padding: 1 }));
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
+ }));
227
261
  }
228
262
  }
@@ -17,6 +17,11 @@ export interface MergeOptions {
17
17
  */
18
18
  arrayMergeStrategy?: ArrayMergeStrategy;
19
19
  }
20
+ /**
21
+ * Deeply merges two YAML-compatible objects.
22
+ * Array handling is controlled by the arrayMergeStrategy option.
23
+ */
24
+ export declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>, options?: MergeOptions): Record<string, unknown>;
20
25
  /**
21
26
  * Merge multiple YAML files into one object.
22
27
  *
@@ -1 +1 @@
1
- {"version":3,"file":"merge-yamls.d.ts","sourceRoot":"","sources":["../../src/utils/merge-yamls.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,QAAQ,GACR;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtB;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAwED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAUlC;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CASlC;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,IAAI,CAAC,WAA+B,EACjD,YAAY,GAAE,YAAiB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAKf"}
1
+ {"version":3,"file":"merge-yamls.d.ts","sourceRoot":"","sources":["../../src/utils/merge-yamls.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,QAAQ,GACR;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtB;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AA4CD;;;GAGG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,GAAE,YAAiB,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBzB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAUlC;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CASlC;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,IAAI,CAAC,WAA+B,EACjD,YAAY,GAAE,YAAiB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAKf"}
@@ -33,7 +33,7 @@ function mergeArraysByKey(target, source, key, mergeOptions) {
33
33
  * Deeply merges two YAML-compatible objects.
34
34
  * Array handling is controlled by the arrayMergeStrategy option.
35
35
  */
36
- function deepMerge(target, source, options = {}) {
36
+ export function deepMerge(target, source, options = {}) {
37
37
  const strategy = options.arrayMergeStrategy ?? "replace";
38
38
  return mergeWith({ ...target }, source, (objValue, srcValue) => {
39
39
  if (Array.isArray(objValue) && Array.isArray(srcValue)) {
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Represents parsed plugin metadata from a Package CRD file.
3
+ */
4
+ export interface PluginMetadata {
5
+ /** The dynamic artifact path (e.g., ./dynamic-plugins/dist/plugin-name) */
6
+ packagePath: string;
7
+ /** The plugin configuration from appConfigExamples[0].content */
8
+ pluginConfig: Record<string, unknown>;
9
+ /** The package name (e.g., @backstage-community/plugin-tech-radar) */
10
+ packageName: string;
11
+ /** Source metadata file path */
12
+ sourceFile: string;
13
+ }
14
+ /**
15
+ * Checks if plugin metadata handling should be enabled.
16
+ * This controls both auto-generation and injection of plugin metadata.
17
+ *
18
+ * Default: ENABLED (for local dev and PR builds)
19
+ * Disabled when:
20
+ * - RHDH_SKIP_PLUGIN_METADATA_INJECTION is set, OR
21
+ * - JOB_NAME contains "periodic-" (nightly/periodic builds)
22
+ */
23
+ export declare function shouldInjectPluginMetadata(): boolean;
24
+ /**
25
+ * Extracts the plugin name from a package path or OCI reference.
26
+ *
27
+ * Handles various formats:
28
+ * - Local path: ./dynamic-plugins/dist/backstage-community-plugin-tech-radar
29
+ * - OCI with integrity: oci://quay.io/rhdh/plugin@sha256:...!backstage-community-plugin-tech-radar
30
+ * - OCI without integrity: oci://quay.io/rhdh/backstage-community-plugin-tech-radar:tag
31
+ *
32
+ * @param packageRef The package reference string
33
+ * @returns The extracted plugin name
34
+ */
35
+ export declare function extractPluginName(packageRef: string): string;
36
+ /**
37
+ * Default metadata directory path relative to the e2e-tests directory.
38
+ * Follows the same pattern as user config paths (e.g., tests/config/dynamic-plugins.yaml).
39
+ */
40
+ export declare const DEFAULT_METADATA_PATH = "../metadata";
41
+ /**
42
+ * Gets the metadata directory path.
43
+ * Uses the provided path or falls back to DEFAULT_METADATA_PATH.
44
+ *
45
+ * @param metadataPath Optional custom path to metadata directory
46
+ * @returns The metadata directory path, or null if it doesn't exist
47
+ */
48
+ export declare function getMetadataDirectory(metadataPath?: string): string | null;
49
+ /**
50
+ * Parses a single metadata YAML file and extracts plugin configuration.
51
+ *
52
+ * @param filePath Path to the metadata YAML file
53
+ * @returns PluginMetadata if valid, null otherwise
54
+ */
55
+ export declare function parseMetadataFile(filePath: string): Promise<PluginMetadata | null>;
56
+ /**
57
+ * Parses all metadata files in a directory and builds a map of plugin name to config.
58
+ * The plugin name is extracted from the dynamicArtifact path for flexible matching.
59
+ *
60
+ * @param metadataDir Path to the metadata directory
61
+ * @returns Map of plugin name to plugin configuration
62
+ */
63
+ export declare function parseAllMetadataFiles(metadataDir: string): Promise<Map<string, PluginMetadata>>;
64
+ /**
65
+ * Plugin entry in dynamic-plugins.yaml
66
+ */
67
+ export interface PluginEntry {
68
+ package: string;
69
+ disabled?: boolean;
70
+ pluginConfig?: Record<string, unknown>;
71
+ [key: string]: unknown;
72
+ }
73
+ /**
74
+ * Dynamic plugins configuration structure
75
+ */
76
+ export interface DynamicPluginsConfig {
77
+ plugins?: PluginEntry[];
78
+ includes?: string[];
79
+ [key: string]: unknown;
80
+ }
81
+ /**
82
+ * Injects plugin configurations from metadata into a dynamic plugins config.
83
+ * Metadata config serves as the base, user-provided pluginConfig overrides it.
84
+ *
85
+ * Matching is done by extracting the plugin name from both the package reference
86
+ * and the metadata's dynamicArtifact, allowing flexible matching across different
87
+ * package formats (local paths, OCI references, etc.).
88
+ *
89
+ * @param dynamicPluginsConfig The dynamic plugins configuration to augment
90
+ * @param metadataMap Map of plugin names to plugin metadata
91
+ * @returns The augmented configuration with injected pluginConfigs
92
+ */
93
+ export declare function injectMetadataConfig(dynamicPluginsConfig: DynamicPluginsConfig, metadataMap: Map<string, PluginMetadata>): DynamicPluginsConfig;
94
+ /**
95
+ * Generates a complete dynamic-plugins configuration from metadata files.
96
+ * Iterates through all metadata files and creates plugin entries with:
97
+ * - package: the dynamicArtifact path
98
+ * - disabled: false (enabled by default)
99
+ * - pluginConfig: from appConfigExamples[0].content
100
+ *
101
+ * @param metadataPath Optional custom path to metadata directory (default: ../metadata)
102
+ * @returns Complete dynamic plugins configuration
103
+ * @throws Error if metadata directory not found or no valid metadata files
104
+ */
105
+ export declare function generateDynamicPluginsConfigFromMetadata(metadataPath?: string): Promise<DynamicPluginsConfig>;
106
+ /**
107
+ * Main function to load and inject plugin metadata for PR builds.
108
+ * For non-PR builds (nightly), returns the config unchanged.
109
+ *
110
+ * @param dynamicPluginsConfig The dynamic plugins configuration
111
+ * @param metadataPath Optional custom path to metadata directory (default: ../metadata)
112
+ * @returns Augmented configuration with metadata (for PR) or unchanged (for nightly)
113
+ * @throws Error if PR build but no metadata directory found
114
+ */
115
+ export declare function loadAndInjectPluginMetadata(dynamicPluginsConfig: DynamicPluginsConfig, metadataPath?: string): Promise<DynamicPluginsConfig>;
116
+ //# sourceMappingURL=plugin-metadata.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,233 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import yaml from "js-yaml";
4
+ import { glob } from "zx";
5
+ import { deepMerge } from "./merge-yamls.js";
6
+ /**
7
+ * Checks if plugin metadata handling should be enabled.
8
+ * This controls both auto-generation and injection of plugin metadata.
9
+ *
10
+ * Default: ENABLED (for local dev and PR builds)
11
+ * Disabled when:
12
+ * - RHDH_SKIP_PLUGIN_METADATA_INJECTION is set, OR
13
+ * - JOB_NAME contains "periodic-" (nightly/periodic builds)
14
+ */
15
+ export function shouldInjectPluginMetadata() {
16
+ // Explicit opt-out
17
+ if (process.env.RHDH_SKIP_PLUGIN_METADATA_INJECTION) {
18
+ console.log("[PluginMetadata] Metadata handling disabled (RHDH_SKIP_PLUGIN_METADATA_INJECTION is set)");
19
+ return false;
20
+ }
21
+ // Periodic/nightly job
22
+ const jobName = process.env.JOB_NAME || "";
23
+ if (jobName.includes("periodic-")) {
24
+ console.log("[PluginMetadata] Metadata handling disabled (periodic job detected)");
25
+ return false;
26
+ }
27
+ return true;
28
+ }
29
+ /**
30
+ * Extracts the plugin name from a package path or OCI reference.
31
+ *
32
+ * Handles various formats:
33
+ * - Local path: ./dynamic-plugins/dist/backstage-community-plugin-tech-radar
34
+ * - OCI with integrity: oci://quay.io/rhdh/plugin@sha256:...!backstage-community-plugin-tech-radar
35
+ * - OCI without integrity: oci://quay.io/rhdh/backstage-community-plugin-tech-radar:tag
36
+ *
37
+ * @param packageRef The package reference string
38
+ * @returns The extracted plugin name
39
+ */
40
+ export function extractPluginName(packageRef) {
41
+ // Strip ! suffix if present (e.g., oci://...@sha256:...!alias)
42
+ const ref = packageRef.includes("!") ? packageRef.split("!")[0] : packageRef;
43
+ // Regex to extract plugin name from various formats:
44
+ // Captures the last path segment (chars except / : @) before any :tag or @digest
45
+ const match = ref.match(/\/([^/:@]+)(?:[:@].*)?$/);
46
+ return match?.[1] || packageRef;
47
+ }
48
+ /**
49
+ * Default metadata directory path relative to the e2e-tests directory.
50
+ * Follows the same pattern as user config paths (e.g., tests/config/dynamic-plugins.yaml).
51
+ */
52
+ export const DEFAULT_METADATA_PATH = "../metadata";
53
+ /**
54
+ * Gets the metadata directory path.
55
+ * Uses the provided path or falls back to DEFAULT_METADATA_PATH.
56
+ *
57
+ * @param metadataPath Optional custom path to metadata directory
58
+ * @returns The metadata directory path, or null if it doesn't exist
59
+ */
60
+ export function getMetadataDirectory(metadataPath = DEFAULT_METADATA_PATH) {
61
+ const resolvedPath = path.resolve(metadataPath);
62
+ if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) {
63
+ console.log(`[PluginMetadata] Using metadata directory: ${resolvedPath}`);
64
+ return resolvedPath;
65
+ }
66
+ console.log(`[PluginMetadata] Metadata directory not found: ${resolvedPath}`);
67
+ return null;
68
+ }
69
+ /**
70
+ * Parses a single metadata YAML file and extracts plugin configuration.
71
+ *
72
+ * @param filePath Path to the metadata YAML file
73
+ * @returns PluginMetadata if valid, null otherwise
74
+ */
75
+ export async function parseMetadataFile(filePath) {
76
+ try {
77
+ const content = await fs.readFile(filePath, "utf8");
78
+ const parsed = yaml.load(content);
79
+ const packagePath = parsed?.spec?.dynamicArtifact;
80
+ const packageName = parsed?.spec?.packageName;
81
+ const pluginConfig = parsed?.spec?.appConfigExamples?.[0]?.content;
82
+ if (!packagePath) {
83
+ console.log(`[PluginMetadata] Skipping ${filePath}: no spec.dynamicArtifact`);
84
+ return null;
85
+ }
86
+ if (!pluginConfig) {
87
+ console.log(`[PluginMetadata] Skipping ${filePath}: no spec.appConfigExamples[0].content`);
88
+ return null;
89
+ }
90
+ console.log(`[PluginMetadata] Loaded metadata for: ${packagePath}`);
91
+ return {
92
+ packagePath,
93
+ pluginConfig,
94
+ packageName: packageName || "",
95
+ sourceFile: filePath,
96
+ };
97
+ }
98
+ catch (error) {
99
+ console.error(`[PluginMetadata] Error parsing ${filePath}:`, error);
100
+ return null;
101
+ }
102
+ }
103
+ /**
104
+ * Parses all metadata files in a directory and builds a map of plugin name to config.
105
+ * The plugin name is extracted from the dynamicArtifact path for flexible matching.
106
+ *
107
+ * @param metadataDir Path to the metadata directory
108
+ * @returns Map of plugin name to plugin configuration
109
+ */
110
+ export async function parseAllMetadataFiles(metadataDir) {
111
+ const pattern = path.join(metadataDir, "*.yaml");
112
+ const files = await glob(pattern);
113
+ console.log(`[PluginMetadata] Found ${files.length} metadata files in ${metadataDir}`);
114
+ const metadataMap = new Map();
115
+ for (const file of files) {
116
+ const metadata = await parseMetadataFile(file);
117
+ if (metadata) {
118
+ // Use extracted plugin name as key for flexible matching
119
+ const pluginName = extractPluginName(metadata.packagePath);
120
+ metadataMap.set(pluginName, metadata);
121
+ console.log(`[PluginMetadata] Mapped plugin: ${pluginName} <- ${metadata.packagePath}`);
122
+ }
123
+ }
124
+ console.log(`[PluginMetadata] Successfully parsed ${metadataMap.size} plugin metadata entries`);
125
+ return metadataMap;
126
+ }
127
+ /**
128
+ * Injects plugin configurations from metadata into a dynamic plugins config.
129
+ * Metadata config serves as the base, user-provided pluginConfig overrides it.
130
+ *
131
+ * Matching is done by extracting the plugin name from both the package reference
132
+ * and the metadata's dynamicArtifact, allowing flexible matching across different
133
+ * package formats (local paths, OCI references, etc.).
134
+ *
135
+ * @param dynamicPluginsConfig The dynamic plugins configuration to augment
136
+ * @param metadataMap Map of plugin names to plugin metadata
137
+ * @returns The augmented configuration with injected pluginConfigs
138
+ */
139
+ export function injectMetadataConfig(dynamicPluginsConfig, metadataMap) {
140
+ if (!dynamicPluginsConfig.plugins) {
141
+ return dynamicPluginsConfig;
142
+ }
143
+ const augmentedPlugins = dynamicPluginsConfig.plugins.map((plugin) => {
144
+ // Extract plugin name from package reference for flexible matching
145
+ const pluginName = extractPluginName(plugin.package);
146
+ const metadata = metadataMap.get(pluginName);
147
+ if (!metadata) {
148
+ // No metadata found for this plugin, keep as-is
149
+ console.log(`[PluginMetadata] No metadata found for: ${pluginName} (from ${plugin.package})`);
150
+ return plugin;
151
+ }
152
+ console.log(`[PluginMetadata] Injecting config for: ${pluginName} (from ${plugin.package})`);
153
+ // Merge: metadata config (base) + user config (override)
154
+ const mergedPluginConfig = deepMerge(metadata.pluginConfig, plugin.pluginConfig || {});
155
+ return {
156
+ ...plugin,
157
+ pluginConfig: mergedPluginConfig,
158
+ };
159
+ });
160
+ return {
161
+ ...dynamicPluginsConfig,
162
+ plugins: augmentedPlugins,
163
+ };
164
+ }
165
+ /**
166
+ * Generates a complete dynamic-plugins configuration from metadata files.
167
+ * Iterates through all metadata files and creates plugin entries with:
168
+ * - package: the dynamicArtifact path
169
+ * - disabled: false (enabled by default)
170
+ * - pluginConfig: from appConfigExamples[0].content
171
+ *
172
+ * @param metadataPath Optional custom path to metadata directory (default: ../metadata)
173
+ * @returns Complete dynamic plugins configuration
174
+ * @throws Error if metadata directory not found or no valid metadata files
175
+ */
176
+ export async function generateDynamicPluginsConfigFromMetadata(metadataPath = DEFAULT_METADATA_PATH) {
177
+ // Skip if metadata handling is disabled
178
+ if (!shouldInjectPluginMetadata()) {
179
+ console.log("[PluginMetadata] Returning empty config (metadata handling disabled)");
180
+ return { plugins: [] };
181
+ }
182
+ console.log("[PluginMetadata] No dynamic-plugins config provided, generating from metadata...");
183
+ // Get metadata directory
184
+ const metadataDir = getMetadataDirectory(metadataPath);
185
+ if (!metadataDir) {
186
+ throw new Error(`[PluginMetadata] Cannot generate dynamic-plugins config: metadata directory not found at: ${path.resolve(metadataPath)}`);
187
+ }
188
+ // Parse all metadata files
189
+ const metadataMap = await parseAllMetadataFiles(metadataDir);
190
+ if (metadataMap.size === 0) {
191
+ throw new Error(`[PluginMetadata] Cannot generate dynamic-plugins config: no valid metadata files found in ${metadataDir}`);
192
+ }
193
+ // Build plugin entries from metadata
194
+ const plugins = [];
195
+ for (const [pluginName, metadata] of metadataMap) {
196
+ console.log(`[PluginMetadata] Adding plugin from metadata: ${pluginName} (${metadata.packagePath})`);
197
+ plugins.push({
198
+ package: metadata.packagePath,
199
+ disabled: false,
200
+ pluginConfig: metadata.pluginConfig,
201
+ });
202
+ }
203
+ console.log(`[PluginMetadata] Generated dynamic-plugins config with ${plugins.length} plugins`);
204
+ return { plugins };
205
+ }
206
+ /**
207
+ * Main function to load and inject plugin metadata for PR builds.
208
+ * For non-PR builds (nightly), returns the config unchanged.
209
+ *
210
+ * @param dynamicPluginsConfig The dynamic plugins configuration
211
+ * @param metadataPath Optional custom path to metadata directory (default: ../metadata)
212
+ * @returns Augmented configuration with metadata (for PR) or unchanged (for nightly)
213
+ * @throws Error if PR build but no metadata directory found
214
+ */
215
+ export async function loadAndInjectPluginMetadata(dynamicPluginsConfig, metadataPath = DEFAULT_METADATA_PATH) {
216
+ // Skip metadata injection if disabled
217
+ if (!shouldInjectPluginMetadata()) {
218
+ return dynamicPluginsConfig;
219
+ }
220
+ console.log("[PluginMetadata] Loading plugin metadata...");
221
+ // Get metadata directory
222
+ const metadataDir = getMetadataDirectory(metadataPath);
223
+ if (!metadataDir) {
224
+ throw new Error(`[PluginMetadata] PR build requires metadata directory but not found at: ${path.resolve(metadataPath)}`);
225
+ }
226
+ // Parse all metadata files
227
+ const metadataMap = await parseAllMetadataFiles(metadataDir);
228
+ if (metadataMap.size === 0) {
229
+ throw new Error(`[PluginMetadata] PR build requires plugin metadata but no valid metadata files found in ${metadataDir}`);
230
+ }
231
+ // Inject metadata configs into the dynamic plugins config
232
+ return injectMetadataConfig(dynamicPluginsConfig, metadataMap);
233
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhdh-e2e-test-utils",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Test utilities for RHDH E2E tests",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",