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.
- package/dist/deployment/rhdh/deployment.d.ts +5 -0
- package/dist/deployment/rhdh/deployment.d.ts.map +1 -1
- package/dist/deployment/rhdh/deployment.js +45 -11
- package/dist/utils/merge-yamls.d.ts +5 -0
- package/dist/utils/merge-yamls.d.ts.map +1 -1
- package/dist/utils/merge-yamls.js +1 -1
- package/dist/utils/plugin-metadata.d.ts +116 -0
- package/dist/utils/plugin-metadata.d.ts.map +1 -0
- package/dist/utils/plugin-metadata.js +233 -0
- package/package.json +1 -1
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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;
|
|
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
|
+
}
|