Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default defineConfig({
{ text: "Examples", link: "/examples/" },
{ text: "Overlay Testing", link: "/overlay/" },
{
text: "v1.1.8",
text: "v1.1.9",
items: [{ text: "Changelog", link: "/changelog" }],
},
],
Expand Down
7 changes: 6 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

All notable changes to this project will be documented in this file.

## [1.1.8] - Current
## [1.1.9] - Current

### Fixed
- **OCI URL replacement with user-provided `dynamic-plugins.yaml`**: When a workspace provides its own `dynamic-plugins.yaml`, plugin package paths were not replaced with OCI URLs for PR builds. Extracted shared `replaceWithOCIUrls()` function so both `generateDynamicPluginsConfigFromMetadata()` and `loadAndInjectPluginMetadata()` code paths now perform OCI replacement when `GIT_PR_NUMBER` is set.

## [1.1.8]

### Fixed
- Fixed namespace deletion race condition during test retries
Expand Down
2 changes: 1 addition & 1 deletion docs/overlay/examples/basic-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ workspaces/<plugin>/e2e-tests/
"eslint-plugin-check-file": "^3.3.1",
"eslint-plugin-playwright": "^2.4.0",
"prettier": "^3.7.4",
"rhdh-e2e-test-utils": "1.1.8",
"rhdh-e2e-test-utils": "1.1.9",
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.0"
}
Expand Down
2 changes: 1 addition & 1 deletion docs/overlay/examples/tech-radar.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ workspaces/tech-radar/e2e-tests/
"eslint-plugin-check-file": "^3.3.1",
"eslint-plugin-playwright": "^2.4.0",
"prettier": "^3.7.4",
"rhdh-e2e-test-utils": "1.1.8",
"rhdh-e2e-test-utils": "1.1.9",
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.0"
}
Expand Down
2 changes: 1 addition & 1 deletion docs/overlay/test-structure/directory-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Defines the test package with dependencies and scripts:
"eslint-plugin-check-file": "^3.3.1",
"eslint-plugin-playwright": "^2.4.0",
"prettier": "^3.7.4",
"rhdh-e2e-test-utils": "1.1.8",
"rhdh-e2e-test-utils": "1.1.9",
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.0"
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rhdh-e2e-test-utils",
"version": "1.1.8",
"version": "1.1.9",
"description": "Test utilities for RHDH E2E tests",
"license": "Apache-2.0",
"type": "module",
Expand Down
162 changes: 87 additions & 75 deletions src/utils/plugin-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,34 +231,32 @@ export function getMetadataDirectory(
*/
export async function parseMetadataFile(
filePath: string,
): Promise<PluginMetadata | null> {
try {
const content = await fs.readFile(filePath, "utf8");
const parsed = yaml.load(content) as PackageCRD;
): Promise<PluginMetadata> {
const content = await fs.readFile(filePath, "utf8");
const parsed = yaml.load(content) as PackageCRD;

const packagePath = parsed?.spec?.dynamicArtifact;
const packageName = parsed?.spec?.packageName;
const pluginConfig = parsed?.spec?.appConfigExamples?.[0]?.content;
const packagePath = parsed?.spec?.dynamicArtifact;
const packageName = parsed?.spec?.packageName;
const pluginConfig = parsed?.spec?.appConfigExamples?.[0]?.content;

if (!packagePath) {
console.log(
`[PluginMetadata] Skipping ${filePath}: no spec.dynamicArtifact`,
);
return null;
}

console.log(`[PluginMetadata] Loaded metadata for: ${packagePath}`);
if (!packagePath) {
throw new Error(
`[PluginMetadata] Missing required field spec.dynamicArtifact in ${filePath}`,
);
}

return {
packagePath,
pluginConfig: pluginConfig || {},
packageName: packageName || "",
sourceFile: filePath,
};
} catch (error) {
console.error(`[PluginMetadata] Error parsing ${filePath}:`, error);
return null;
if (!packageName) {
throw new Error(
`[PluginMetadata] Missing required field spec.packageName in ${filePath}`,
);
}

return {
packagePath,
pluginConfig: pluginConfig || {},
packageName,
sourceFile: filePath,
};
}

/**
Expand All @@ -282,14 +280,11 @@ export async function parseAllMetadataFiles(

for (const file of files) {
const metadata = await parseMetadataFile(file);
if (metadata) {
// Use extracted plugin name as key for flexible matching
const pluginName = extractPluginName(metadata.packagePath);
metadataMap.set(pluginName, metadata);
console.log(
`[PluginMetadata] Mapped plugin: ${pluginName} <- ${metadata.packagePath}`,
);
}
const pluginName = extractPluginName(metadata.packagePath);
metadataMap.set(pluginName, metadata);
console.log(
`[PluginMetadata] Mapped plugin: ${pluginName} <- ${metadata.packagePath}`,
);
}

console.log(
Expand Down Expand Up @@ -318,6 +313,48 @@ export interface DynamicPluginsConfig {
[key: string]: unknown;
}

/**
* Replaces local package paths with OCI URLs for plugins that have matching metadata.
* Only applies to plugins with metadata (workspace plugins). Plugins without metadata
* (e.g., keycloak, bulk-import from auth defaults) keep their original paths.
*
* @param plugins The plugin entries to process
* @param metadataMap Map of plugin names to plugin metadata
* @param metadataPath Path to the metadata directory (used to resolve workspace root)
* @returns Plugin entries with OCI URLs replaced where applicable
*/
async function replaceWithOCIUrls(
plugins: PluginEntry[],
metadataMap: Map<string, PluginMetadata>,
metadataPath: string,
): Promise<PluginEntry[]> {
const prNumber = process.env.GIT_PR_NUMBER;
if (!prNumber) {
return plugins;
}

console.log(
`[PluginMetadata] PR build detected (PR #${prNumber}), fetching OCI URLs...`,
);
const workspacePath = path.resolve(metadataPath, "..");
const ociUrls = await getOCIUrlsForPR(workspacePath, prNumber);

return plugins.map((plugin) => {
const pluginName = extractPluginName(plugin.package);
const metadata = metadataMap.get(pluginName);
if (!metadata?.packageName) return plugin;

const displayName = metadata.packageName
.replace(/^@/, "")
.replace(/\//g, "-");
const ociUrl = ociUrls.get(displayName);
if (!ociUrl) return plugin;

console.log(`[PluginMetadata] Replacing ${plugin.package} with ${ociUrl}`);
return { ...plugin, package: ociUrl };
});
}

/**
* Injects plugin configurations from metadata into a dynamic plugins config.
* Metadata config serves as the base, user-provided pluginConfig overrides it.
Expand Down Expand Up @@ -417,60 +454,24 @@ export async function generateDynamicPluginsConfigFromMetadata(
);
}

// If PR number is set, fetch OCI URLs
const prNumber = process.env.GIT_PR_NUMBER;
let ociUrls: Map<string, string> | null = null;
if (prNumber) {
console.log(
`[PluginMetadata] PR build detected (PR #${prNumber}), fetching OCI URLs...`,
);
const workspacePath = path.resolve(metadataPath, "..");
ociUrls = await getOCIUrlsForPR(workspacePath, prNumber);
}

// Build plugin entries from metadata
const plugins: PluginEntry[] = [];
let plugins: PluginEntry[] = [];

for (const [pluginName, metadata] of metadataMap) {
let packageRef = metadata.packagePath;

// Replace with OCI URL if available (required for PR builds)
if (ociUrls) {
if (!metadata.packageName) {
throw new Error(
`[PluginMetadata] PR build requires packageName in metadata but not found for: ${pluginName}\n` +
` Source file: ${metadata.sourceFile}`,
);
}

const displayName = metadata.packageName
.replace(/^@/, "")
.replace(/\//g, "-");
const ociUrl = ociUrls.get(displayName);

if (!ociUrl) {
throw new Error(
`[PluginMetadata] PR build requires OCI URL but not found for: ${displayName}\n` +
` Package name: ${metadata.packageName}\n` +
` Source file: ${metadata.sourceFile}`,
);
}

console.log(`[PluginMetadata] Replacing ${packageRef} with ${ociUrl}`);
packageRef = ociUrl;
}

console.log(
`[PluginMetadata] Adding plugin: ${pluginName} (${packageRef})`,
`[PluginMetadata] Adding plugin: ${pluginName} (${metadata.packagePath})`,
);

plugins.push({
package: packageRef,
package: metadata.packagePath,
disabled: false,
pluginConfig: metadata.pluginConfig,
});
}

// Replace local paths with OCI URLs for PR builds
plugins = await replaceWithOCIUrls(plugins, metadataMap, metadataPath);

console.log(
`[PluginMetadata] Generated dynamic-plugins config with ${plugins.length} plugins`,
);
Expand Down Expand Up @@ -517,5 +518,16 @@ export async function loadAndInjectPluginMetadata(
}

// Inject metadata configs into the dynamic plugins config
return injectMetadataConfig(dynamicPluginsConfig, metadataMap);
const result = injectMetadataConfig(dynamicPluginsConfig, metadataMap);

// Replace local paths with OCI URLs for PR builds
if (result.plugins) {
result.plugins = await replaceWithOCIUrls(
result.plugins,
metadataMap,
metadataPath,
);
}

return result;
}