Skip to content

DevHub plugin catalog - #3422

Merged
gazarenkov merged 37 commits into
redhat-developer:mainfrom
gazarenkov:devhub-plugin-catalog
Sep 8, 2026
Merged

gazarenkov merged 37 commits into
redhat-developer:mainfrom
gazarenkov:devhub-plugin-catalog

Conversation

@gazarenkov

Copy link
Copy Markdown
Member

Description

  • Add DevHubPluginCatalog controller for cluster-scoped plugin catalog management with default catalog support
  • Add OCI fetcher package for pulling plugin images from registries with auth support
  • Add --disable-catalog-controller flag for local development

Changes

DevHubPluginCatalog Controller

  • New cluster-scoped CRD DevHubPluginCatalog for managing plugin catalogs
  • Processes catalog images and generates ConfigMaps in operator namespace
  • Supports pull secrets for private registries
  • Default catalog automatically created from operator's catalog-index image

OCI Fetcher (pkg/fetcher/)

  • Fetch OCI artifacts from container registries
  • Support for Docker config auth, CA certs, and insecure registries
  • Extract plugin metadata from OCI annotations

Documentation

  • docs/developer.md: Added note about DevHubPluginCatalog controller disabled by default for local development
  • docs/dynamic-plugins.md: DevHubPluginCatalog usage and configuration

Test plan

  • Unit tests pass (make test)
  • Integration tests pass (make integration-test)
  • DevHubPluginCatalog creates ConfigMap from catalog image
  • Default catalog is created on operator startup
  • Private registry with pullSecret works

Which issue(s) does this PR fix or relate to

  • Fixes #issue_number

PR acceptance criteria

  • Tests
  • Documentation

Building Container Images for Testing

Need to test container images from this PR?

For Maintainers: To trigger a test image build, review the code and comment /build-images.
This always builds the HEAD of the PR branch.

For Contributors: Ask a maintainer to run /build-images.

Images will be built and pushed to Quay with links posted in comments.

@gazarenkov
gazarenkov requested a review from a team as a code owner August 25, 2026 14:32
Comment thread pkg/fetcher/extract.go Fixed
@rhdh-qodo-merge

Copy link
Copy Markdown

PR Summary by Qodo

Add DevHub plugin catalog controller with OCI registry support

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add cluster-scoped plugin catalogs with authenticated OCI fetching and secure extraction.
• Merge catalog configurations before Backstage reconciliation and reject duplicate plugin names.
• Ship default catalog manifests, deployment wiring, documentation, and comprehensive tests.
Diagram

graph TD
  A["Catalog CR"] --> B["Catalog Controller"] --> C["Catalog Processor"] --> D["OCI Fetcher"] --> E["OCI Registry"]
  F["Registry Credentials"] --> B
  C --> G["Default ConfigMap"] --> H["Backstage Controller"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Retain init-container catalog processing
  • ➕ Reuses the established catalog-index environment-variable flow.
  • ➕ Keeps registry pulling outside the operator reconciliation path.
  • ➖ Cannot centrally aggregate cluster-scoped catalog resources.
  • ➖ Duplicates catalog processing per Backstage workload and offers weaker lifecycle coordination.
2. Create one ConfigMap per catalog
  • ➕ Isolates failures and ownership for individual catalog sources.
  • ➕ Allows incremental updates without rebuilding every catalog output.
  • ➖ Requires additional composition logic in each Backstage reconciliation.
  • ➖ Makes duplicate detection and deterministic ordering more complex.

Recommendation: The controller-based aggregate ConfigMap is the best fit for cluster-wide catalog management because it centralizes authenticated fetching, duplicate validation, and readiness coordination. Retaining init-container processing as the documented fallback for OPERATOR_DP_PROCESSING=false preserves compatibility without making it the primary architecture.

Files changed (38) +2751 / -86

Enhancement (10) +936 / -0
current-types.goExpose current DevHubPluginCatalog API aliases +6/-0

Expose current DevHubPluginCatalog API aliases

• Adds aliases for the catalog resource, list, spec, and source types through the current API package.

api/current-types.go

devhubplugincatalog_types.goDefine the cluster-scoped plugin catalog API +61/-0

Define the cluster-scoped plugin catalog API

• Introduces DevHubPluginCatalog and its list/spec types. Catalog sources support OCI references, pull Secrets, custom CA ConfigMaps, and optional TLS verification bypass.

api/v1alpha5/devhubplugincatalog_types.go

zz_generated.deepcopy.goGenerate deep-copy methods for catalog API types +100/-0

Generate deep-copy methods for catalog API types

• Adds Kubernetes deep-copy and runtime object implementations for DevHubPluginCatalog, its list, spec, and nested source references.

api/v1alpha5/zz_generated.deepcopy.go

main.goRegister the optional catalog controller +34/-0

Register the optional catalog controller

• Starts DevHubPluginCatalog reconciliation with the detected operator namespace. Adds a command-line and environment toggle for disabling the controller during local development.

cmd/main.go

backstage_controller.goGate Backstage reconciliation on catalog readiness +31/-0

Gate Backstage reconciliation on catalog readiness

• Checks the mounted catalog readiness marker before building Backstage resources and requeues while catalog processing remains incomplete. Missing status files retain local-development compatibility.

internal/controller/backstage_controller.go

devhubplugincatalog_controller.goReconcile cluster catalogs into operator configuration +134/-0

Reconcile cluster catalogs into operator configuration

• Lists every catalog, resolves pull Secrets and CA ConfigMaps from the operator namespace, processes all sources, and patches the default configuration ConfigMap.

internal/controller/devhubplugincatalog_controller.go

processor.goFetch, validate, and merge plugin catalogs +157/-0

Fetch, validate, and merge plugin catalogs

• Downloads each catalog to temporary storage, parses plugin configuration, rejects duplicate or invalid plugin names, and creates the dynamic-plugins ConfigMap patch with a readiness marker.

pkg/catalog/processor.go

extract.goSafely extract OCI layer archives +116/-0

Safely extract OCI layer archives

• Adds tar extraction with traversal and symlink escape checks, configurable per-entry size limits, and support helpers for compressed and uncompressed archives.

pkg/fetcher/extract.go

oci.goAdd authenticated OCI artifact fetching +281/-0

Add authenticated OCI artifact fetching

• Introduces an OCI registry client supporting Docker configuration credentials, custom CAs, insecure TLS, proxy-aware transports, digest reporting, layer extraction, and optional plugin annotation validation.

pkg/fetcher/oci.go

utils.goAdd integer and string environment helpers +16/-0

Add integer and string environment helpers

• Introduces default-aware helpers for reading integer and string environment variables.

pkg/utils/utils.go

Bug fix (1) +11 / -3
dynamic-plugins.goPreserve installer image and skip local plugin downloads +11/-3

Preserve installer image and skip local plugin downloads

• Renames the installer environment variable, excludes image-bundled local plugin paths from package downloads, and prevents the Backstage image override from replacing the dedicated installer during operator processing.

pkg/model/dynamic-plugins.go

Refactor (2) +7 / -6
backstage_types.goRelocate Backstage scheme registration +4/-4

Relocate Backstage scheme registration

• Moves the existing Backstage scheme registration initializer to the end of the file without changing behavior.

api/v1alpha5/backstage_types.go

deployment.goResolve plugin-installer image before deployment mutation +3/-2

Resolve plugin-installer image before deployment mutation

• Reads operator dynamic-plugin processing and the installer image once before conditionally overriding the init container.

pkg/model/deployment.go

Tests (7) +1235 / -2
devhubplugincatalog_test.goVerify end-to-end catalog ConfigMap generation +111/-0

Verify end-to-end catalog ConfigMap generation

• Creates a catalog resource, invokes reconciliation against a real catalog image, and verifies the default ConfigMap receives plugin content and the readiness marker.

integration_tests/devhubplugincatalog_test.go

devhubplugincatalog_controller_test.goTest catalog controller inputs and ConfigMap patching +312/-0

Test catalog controller inputs and ConfigMap patching

• Covers empty and multiple catalogs, TLS bypass, pull Secrets, explicit and default CA keys, missing references, and ConfigMap merge patches.

internal/controller/devhubplugincatalog_controller_test.go

processor_test.goTest catalog merging and output generation +314/-0

Test catalog merging and output generation

• Covers single and multiple merges, duplicate detection, invalid content, preserved plugin configuration, YAML wrapping, indentation, and patch structure.

pkg/catalog/processor_test.go

extract_test.goTest secure archive extraction +201/-0

Test secure archive extraction

• Verifies nested and compressed extraction, traversal rejection, symlink containment, empty archives, and configurable entry limits.

pkg/fetcher/extract_test.go

oci_test.goTest OCI authentication and transport options +295/-0

Test OCI authentication and transport options

• Covers fetcher construction, TLS options, Docker credential resolution formats, plugin validation settings, option precedence, and result metadata.

pkg/fetcher/oci_test.go

deployment_test.goAlign deployment test expectations with related image naming +1/-1

Align deployment test expectations with related image naming

• Updates test documentation to reflect RELATED_IMAGE_plugin_installer as the operator-processing init-container image.

pkg/model/deployment_test.go

model_tests.goSeed plugin-installer related image in model tests +1/-1

Seed plugin-installer related image in model tests

• Updates the shared model test setup to provide the renamed installer image environment variable.

pkg/model/model_tests.go

Documentation (4) +114 / -26
deployment.yamlDocument the plugin-installer related image override +1/-1

Document the plugin-installer related image override

• Updates the dynamic-plugin init-container comment to reference RELATED_IMAGE_plugin_installer.

config/profile/rhdh/default-config/deployment.yaml

developer.mdDocument local catalog-controller behavior +2/-0

Document local catalog-controller behavior

• Explains that local run and test workflows disable catalog reconciliation to avoid conflicts with an installed operator.

docs/developer.md

dynamic-plugins.mdDocument DevHubPluginCatalog configuration +110/-24

Document DevHubPluginCatalog configuration

• Describes default and additional catalogs, private registry authentication, custom CAs, insecure TLS, proxies, refresh annotations, duplicate handling, and the legacy init-container mode.

docs/dynamic-plugins.md

README.mdDocument renamed plugin-installer image variable +1/-1

Document renamed plugin-installer image variable

• Updates the custom plugin-installer build example to use RELATED_IMAGE_plugin_installer.

plugin-installer/README.md

Other (14) +448 / -49
MakefileWire catalog and plugin-installer settings into build workflows +30/-18

Wire catalog and plugin-installer settings into build workflows

• Renames the plugin-installer image variable to its related-image convention, disables catalog reconciliation for local tests and runs, and substitutes catalog settings into generated installers, bundles, and deployments. It also temporarily switches the RHDH operator image base for arm64 support.

Makefile

backstage-operator.clusterserviceversion.yamlPublish catalog API and runtime requirements in community CSV +25/-1

Publish catalog API and runtime requirements in community CSV

• Declares the catalog CRD, read permissions, pod identity environment variables, and writable temporary storage in the community operator bundle.

bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml

backstage-operator.clusterserviceversion.yamlPublish default catalog and controller wiring in RHDH CSV +44/-8

Publish default catalog and controller wiring in RHDH CSV

• Adds the catalog owned CRD and default sample resource, controller RBAC, pod namespace wiring, temporary storage, operator processing settings, and plugin-installer related image.

bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml

rhdh-default-config_v1_configmap.yamlAlign bundled dynamic-plugin catalog image settings +2/-2

Align bundled dynamic-plugin catalog image settings

• Updates plugin-installer documentation and pins the bundled catalog-index image to the 2.0.0 release.

bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml

kustomization.yamlInclude the catalog CRD in generated manifests +1/-0

Include the catalog CRD in generated manifests

• Adds the DevHubPluginCatalog CRD base to the CRD kustomization resources.

config/crd/kustomization.yaml

deployment.yamlProvide namespace identity and temporary storage to manager +14/-1

Provide namespace identity and temporary storage to manager

• Injects pod name and namespace through the downward API and mounts a writable emptyDir at /tmp for OCI extraction.

config/manager/deployment.yaml

catalog.yamlDefine the primary RHDH plugin catalog +8/-0

Define the primary RHDH plugin catalog

• Adds the default DevHubPluginCatalog resource whose OCI reference is replaced with the selected catalog image during manifest generation.

config/profile/rhdh/default-config/catalog.yaml

kustomization.yamlInstall the default catalog in the RHDH profile +3/-2

Install the default catalog in the RHDH profile

• Includes the primary catalog resource and switches the profile's controller image to the community 2.0.0 build.

config/profile/rhdh/kustomization.yaml

deployment-patch.yamlConfigure operator-side dynamic-plugin processing images +4/-6

Configure operator-side dynamic-plugin processing images

• Adds templated operator processing and plugin-installer related-image environment variables while relying on the base deployment for pod identity fields.

config/profile/rhdh/patches/deployment-patch.yaml

role.yamlGrant catalog watch permissions +8/-0

Grant catalog watch permissions

• Allows the operator role to get, list, and watch cluster-scoped DevHubPluginCatalog resources.

config/rbac/role.yaml

install.yamlRegenerate community installation manifest for catalogs +135/-0

Regenerate community installation manifest for catalogs

• Adds the catalog CRD and RBAC to the consolidated installer, plus pod identity variables and writable temporary storage for the manager.

dist/backstage.io/install.yaml

install.yamlRegenerate RHDH installer with default catalog support +150/-9

Regenerate RHDH installer with default catalog support

• Adds the catalog CRD, RBAC, default catalog resource, pinned catalog image, related-image settings, pod identity, and temporary extraction storage.

dist/rhdh/install.yaml

go.modAdd OCI registry client dependency +8/-0

Add OCI registry client dependency

• Adds go-containerregistry and the transitive packages required for OCI transport, authentication, image formats, and compression.

go.mod

go.sumLock OCI fetcher dependency checksums +16/-2

Lock OCI fetcher dependency checksums

• Records checksums for go-containerregistry and its newly resolved transitive dependencies.

go.sum

@rhdh-qodo-merge

rhdh-qodo-merge Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (1) 🔗 Cross-repo conflicts (4) 📜 Skill insights (0)

Grey Divider


Action required

1. Multi-plugin images rejected as duplicates ✗ Dismissed 🔗 Cross-repo conflict ≡ Correctness ⭐ New
Description
AddCatalog keys duplicate detection by DynaPlugin.Name(), which strips the !<plugin-path>
suffix, so distinct entries from one multi-plugin OCI image collide and fail reconciliation. RHDH
documents this suffix for multi-plugin images, and rhdh-plugins includes the plugin path in its
package identity so each entry is installed separately.
Code

pkg/catalog/processor.go[54]

+		name := plugin.Name()
Relevance

●●● Strong

Recent accepted precedent fixed OCI name parsing; preserving multi-plugin path identity is a closely
matching correctness issue.

PR-#3215

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR derives its duplicate key from DynaPlugin.Name(), whose implementation removes the
plugin-path suffix before reducing an OCI reference to its image name. The RHDH documentation
defines !<plugin-path> for selecting distinct plugins from a multi-plugin image, while the
rhdh-plugins installer includes that path in its plugin key.

pkg/catalog/processor.go[53-61]
pkg/model/dynamic-plugins-reference.go[155-190]
External repo: redhat-developer/rhdh, docs/dynamic-plugins/installing-plugins.md [150-170]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/oci-key.ts [52-67]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/oci-key.ts [71-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Valid catalog entries that reference different plugins within the same OCI image are incorrectly rejected as duplicates because duplicate detection uses a name that omits the `!<plugin-path>` suffix.

## Issue Context
RHDH and the rhdh-plugins installer support multi-plugin OCI images and distinguish entries by both image reference and plugin path. Preserve that distinction while still rejecting genuine duplicate plugin entries.

## Fix Focus Areas
- pkg/catalog/processor.go[53-61]
- pkg/model/dynamic-plugins-reference.go[155-190]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Foreign namespaces alter RHDH plugins ✓ Resolved 🔗 Cross-repo conflict ≡ Correctness
Description
Although DevHubPluginCatalog is namespaced and documented as operator-namespace-only, the
reconciler lists catalogs cluster-wide, merges resources from every namespace into the operator
namespace's rhdh-default-config, and resolves their Secret and CA ConfigMap references from the
operator namespace rather than their own. Because the rhdh-plugins installer loads this generated
dynamic-plugin configuration, an out-of-scope catalog can alter the defaults and installed plugins
for every operator-managed RHDH instance while being unable to use references from its own
namespace.
Code

internal/controller/devhubplugincatalog_controller.go[49]

+	if err := r.List(ctx, catalogList); err != nil {
Relevance

●●● Strong

Cross-namespace scoping bug in namespaced CRD reconciler is a real correctness issue teams accept
fixing.

PR-#3348
PR-#1651

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The API declares DevHubPluginCatalog as namespaced, while the documentation and Backstage
readiness checks restrict supported catalogs to the operator namespace; however, both controller
list operations omit client.InNamespace, and every returned catalog is processed into the operator
namespace's shared configuration with its references forcibly read from that namespace. The
cross-repository installer then loads the resulting configuration and categorizes its plugin entries
for installation, demonstrating how an out-of-scope catalog can affect operator-managed instances.

docs/dynamic-plugins.md[50-68]
internal/controller/devhubplugincatalog_controller.go[47-54]
internal/controller/devhubplugincatalog_controller.go[93-100]
api/v1alpha5/devhubplugincatalog_types.go[56-60]
internal/controller/devhubplugincatalog_controller.go[47-50]
internal/controller/devhubplugincatalog_controller.go[86-124]
internal/controller/devhubplugincatalog_controller.go[133-140]
internal/controller/backstage_controller.go[222-230]
docs/dynamic-plugins.md[50-65]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/installer.ts [127-149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description

`DevHubPluginCatalog` is a namespaced, operator-namespace-only API, but the reconciler lists and aggregates catalogs cluster-wide into the operator namespace's shared configuration. This allows catalogs from unrelated namespaces to alter generated RHDH plugin defaults, while their Secret and CA ConfigMap references are incorrectly resolved from the operator namespace.

## Issue Context

Documentation and Backstage readiness checks define catalogs as operator-namespace resources. Apply `client.InNamespace(r.OperatorNamespace)` consistently to catalog list operations in both reconciliation and input construction, and consider filtering controller events to the operator namespace as well.

## Fix Focus Areas

- api/v1alpha5/devhubplugincatalog_types.go[56-60]
- internal/controller/devhubplugincatalog_controller.go[47-54]
- internal/controller/devhubplugincatalog_controller.go[86-100]
- internal/controller/devhubplugincatalog_controller.go[103-124]
- internal/controller/devhubplugincatalog_controller.go[198-204]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Local-only plugins crash installer ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new filter omits local plugins from packages.txt but still creates the file, so a
configuration containing only built-in/local plugins produces an empty input. Under the installer's
set -euo pipefail, total=$(grep -cv ...) returns status 1 for that empty file and terminates the
init container.
Code

pkg/model/dynamic-plugins.go[R149-154]

+				// Skip local paths - they're built into the image and don't need downloading
+				if strings.HasPrefix(plugin.Package, "./") || strings.HasPrefix(plugin.Package, "/") {
+					klog.V(1).Infof("skipping local path plugin %q (built into image)", plugin.Package)
+					continue
+				}
				packages = append(packages, plugin.Package)
Relevance

●●● Strong

Filtering all packages can produce an empty packages file that strict shell handling cannot process
successfully.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The model always creates packages.txt after filtering, and the shell assignment inherits grep's
nonzero result for an empty file while errexit is enabled.

pkg/model/dynamic-plugins.go[145-168]
pkg/model/dynamic-plugins.go[199-205]
plugin-installer/install_plugins.sh[700-736]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Filtering every enabled plugin as local creates an empty packages file that causes the installer to exit.

## Issue Context
Either skip the installer for an empty remote package set or make the script explicitly succeed with zero packages.

## Fix Focus Areas
- pkg/model/dynamic-plugins.go[145-168]
- plugin-installer/install_plugins.sh[700-736]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (7)
4. Unbounded layer memory usage ✓ Resolved 🐞 Bug ☼ Reliability
Description
FetchContent buffers the entire uncompressed OCI layer with io.ReadAll before extraction,
allowing a large or highly compressed layer to exhaust the operator pod's memory. MAX_ENTRY_SIZE
is applied only afterward to individual tar entries and therefore does not bound this allocation.
Code

pkg/fetcher/oci.go[R162-169]

+	reader, err := layers[0].Uncompressed()
+	if err != nil {
+		return nil, fmt.Errorf("failed to uncompress layer: %w", err)
+	}
+	defer func() { _ = reader.Close() }()
+
+	content, err := io.ReadAll(reader)
+	if err != nil {
Relevance

●●● Strong

io.ReadAll defeats the configured extraction limit and creates a straightforward resource-exhaustion
risk for untrusted OCI content.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The complete uncompressed layer is read into one byte slice; the only configured size limit is
inside the subsequent tar file copy.

pkg/fetcher/oci.go[162-170]
pkg/fetcher/extract.go[108-115]
pkg/catalog/processor.go[46-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The OCI fetcher buffers an unbounded uncompressed layer in memory before applying extraction limits.

## Issue Context
Stream layer data into extraction and enforce both per-entry and total uncompressed-size limits.

## Fix Focus Areas
- pkg/fetcher/oci.go[162-170]
- pkg/fetcher/extract.go[49-115]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Deleted catalogs remain active ✓ Resolved 🐞 Bug ≡ Correctness
Description
Deleting the final DevHubPluginCatalog returns early without clearing dynamic-plugins.yaml or
the readiness marker from rhdh-default-config. Plugins from the deleted catalog therefore remain
in the operator's generated defaults.
Code

internal/controller/devhubplugincatalog_controller.go[R47-50]

+	if len(inputs) == 0 {
+		lg.Info("No DevHubPluginCatalog resources found")
+		return ctrl.Result{}, nil
+	}
Relevance

●●● Strong

Returning without clearing generated state leaves deleted catalog plugins active; this is a direct
reconciliation correctness bug.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ConfigMap mutation occurs only after non-empty processing, so the zero-item deletion reconcile
cannot remove previously written keys.

internal/controller/devhubplugincatalog_controller.go[41-61]
internal/controller/devhubplugincatalog_controller.go[114-125]
pkg/catalog/processor.go[61-70]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The zero-catalog reconciliation path leaves the last merged catalog configuration in place.

## Issue Context
Define the desired empty-catalog state and patch the ConfigMap accordingly instead of returning without mutation.

## Fix Focus Areas
- internal/controller/devhubplugincatalog_controller.go[41-61]
- pkg/catalog/processor.go[61-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Certified bundle runs community image ✗ Dismissed 🐞 Bug ≡ Correctness
Description
The RHDH CSV still advertises the certified registry.redhat.io operator image but now deploys
quay.io/rhdh-community/operator:2.0.0. This makes the shipped certified bundle execute a different
image than its metadata declares and bypasses the supported RHDH operator image.
Code

bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml[357]

+                image: quay.io/rhdh-community/operator:2.0.0
Relevance

●●● Strong

Certified metadata and deployed image conflict; release bundle image consistency is a correctness
issue.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Within the same generated certified CSV, containerImage points to the supported registry while the
manager deployment points to the newly selected community image.

bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml[41-45]
bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml[339-357]
config/profile/rhdh/kustomization.yaml[16-19]
Makefile[35-50]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The certified RHDH bundle deploys a community operator image while declaring the supported registry image in metadata.

## Issue Context
Keep development/multi-architecture overrides out of release bundle generation, or update the release contract consistently if this change is intentional.

## Fix Focus Areas
- Makefile[35-50]
- config/profile/rhdh/kustomization.yaml[16-19]
- bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml[41-45]
- bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml[357-357]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Nested ConfigMap hides plugins 🔗 Cross-repo conflict ≡ Correctness
Description
The controller writes an entire ConfigMap manifest into the existing ConfigMap's
dynamic-plugins.yaml value, so the operator and rhdh-plugins installer parse a document whose root
has apiVersion/kind/data rather than the expected plugins list. With operator dynamic-plugin
processing enabled, this yields no enabled packages, so catalog plugins are not installed.
Code

pkg/catalog/processor.go[R62-65]

+	wrapped := p.wrapInConfigMap(merged)
+	patchData := map[string]interface{}{
+		"data": map[string]string{
+			model.DynamicPluginsFile: string(wrapped),
Relevance

●●● Strong

Wrapping plugin configuration as a nested ConfigMap changes the expected document shape and prevents
installer plugin parsing.

PR-#1486

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR wraps merged plugin YAML in a second ConfigMap before assigning it to dynamic-plugins.yaml.
The operator reads that value directly as DynaPluginsConfig, while rhdh-plugins defines and parses
the value as a configuration with top-level includes and plugins.

pkg/catalog/processor.go[61-81]
pkg/model/dynamic-plugins.go[254-271]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/types.ts [68-77]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/installer.ts [319-329]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The catalog processor currently stores a nested ConfigMap manifest in the `dynamic-plugins.yaml` data key. Store the merged dynamic-plugin configuration directly so operator and installer consumers can parse its `plugins` and `includes` fields.

## Issue Context
`dynamic-plugins.yaml` is already a ConfigMap data value; it must not contain another ConfigMap wrapper.

## Fix Focus Areas
- pkg/catalog/processor.go[61-82]
- pkg/catalog/processor_test.go[178-207]
- integration_tests/devhubplugincatalog_test.go[94-108]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Failed refresh remains ready ✓ Resolved 🐞 Bug ≡ Correctness
Description
When a changed or newly added catalog fails to fetch or merge, reconciliation returns without
clearing the previous .catalogs-ready=true marker or catalog content. Backstage reconciliations
consequently continue using stale data while the catalog set is not actually ready.
Code

internal/controller/devhubplugincatalog_controller.go[R52-55]

+	// 2. Process all catalogs (fetch and merge)
+	content, err := r.Processor.Process(ctx, inputs)
+	if err != nil {
+		return ctrl.Result{}, fmt.Errorf("failed to process catalogs: %w", err)
Relevance

●●● Strong

Returning errors without invalidating readiness permits stale catalog configuration to remain active
after failed refreshes.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The controller returns immediately on processing failure, while the only marker mutation in the
feature writes true; no path clears or invalidates an earlier successful marker.

internal/controller/devhubplugincatalog_controller.go[52-61]
pkg/catalog/processor.go[61-70]
internal/controller/backstage_controller.go[94-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Catalog refresh failures leave the previous readiness marker and merged configuration active.

## Issue Context
Set a pending/not-ready state before processing and only publish readiness atomically with successfully merged content; expose the failure on the catalog resource as appropriate.

## Fix Focus Areas
- internal/controller/devhubplugincatalog_controller.go[37-64]
- pkg/catalog/processor.go[61-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Missing marker means ready ✓ Resolved 🐞 Bug ≡ Correctness
Description
areCatalogsReady treats an absent .catalogs-ready file as success, but the shipped default
ConfigMap initially has no such key. On operator startup, Backstage reconciliation can therefore
proceed before the catalog controller has fetched and patched the catalog configuration.
Code

internal/controller/backstage_controller.go[R219-223]

+	info, err := os.Stat(CatalogStatusPath)
+	if os.IsNotExist(err) {
+		// No file → local dev → ready
+		return true
+	}
Relevance

●●● Strong

Treating the missing readiness marker as ready defeats the newly introduced startup gating and can
race catalog reconciliation.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Backstage reconciliation is gated by this helper, yet the helper returns true for the initial state
before the catalog reconciler writes the marker.

internal/controller/backstage_controller.go[94-99]
internal/controller/backstage_controller.go[213-230]
internal/controller/devhubplugincatalog_controller.go[52-61]
pkg/catalog/processor.go[61-70]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A missing readiness marker is interpreted as ready in deployed mode, creating a startup race with catalog generation.

## Issue Context
Distinguish disabled/local mode explicitly; when the catalog controller is enabled, require a positive readiness marker before reconciling Backstage resources.

## Fix Focus Areas
- internal/controller/backstage_controller.go[213-230]
- cmd/main.go[300-314]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Cluster resource gains namespace ⊘ Outdated 🐞 Bug ≡ Correctness
Description
The generated DevHubPluginCatalog manifest includes metadata.namespace even though its CRD is
cluster-scoped. Applying the RHDH installer can therefore reject the default catalog object, and the
CSV example incorrectly models the API scope.
Code

dist/rhdh/install.yaml[R4252-4256]

+apiVersion: rhdh.redhat.com/v1alpha5
+kind: DevHubPluginCatalog
+metadata:
+  name: rhdh-primary-catalog
+  namespace: rhdh-operator
Relevance

●●● Strong

Generated cluster-scoped resources must not carry namespaces; analogous generated CRD correctness
fixes were accepted.

PR-#1406

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The API marker and generated CRD declare cluster scope, but both generated RHDH artifacts contain a
namespace on the new object.

api/v1alpha5/devhubplugincatalog_types.go[36-43]
dist/rhdh/install.yaml[4252-4259]
bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml[26-38]
config/profile/rhdh/kustomization.yaml[1-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Kustomize adds a namespace to the cluster-scoped default DevHubPluginCatalog.

## Issue Context
Ensure generated install and CSV example objects omit `metadata.namespace` while preserving namespacing for namespaced resources.

## Fix Focus Areas
- config/profile/rhdh/kustomization.yaml[1-14]
- config/profile/rhdh/default-config/catalog.yaml[1-8]
- dist/rhdh/install.yaml[4252-4259]
- bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml[26-38]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

11. Merge Strategy misstates ordering 📘 Rule violation ✧ Quality ⭐ New
Description
The documentation promises that plugins are appended in alphabetical catalog-resource order, but the
controller does not sort catalogs and BuildPatch emits plugins by ranging over a Go map, making
their order unspecified. Because enabled plugins’ pluginConfig maps are merged sequentially in
emitted slice order and later scalar values overwrite earlier ones, identical catalogs with
overlapping keys can produce different effective Backstage configuration across reconciliations.
Code

docs/dynamic-plugins.md[76]

+- Plugin entries are **appended** in catalog processing order (alphabetical by resource name)
Relevance

●●● Strong

Recent accepted precedents favor correcting inaccurate dynamic-plugin documentation and explicitly
documenting nondeterministic ordering.

PR-#2717
PR-#2000

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 6 requires documented merge behavior to match the implementation, yet the changed
documentation promises alphabetical catalog-resource processing while the controller iterates an
unsorted list and the processor converts plugin entries to a map before iterating that map into the
serialized output slice. Downstream, DynamicPlugins.addToModel preserves this slice order,
AppConfig.addPluginsAppConfig merges plugin configurations sequentially, and mergePluginConfigs
overwrites non-map values, demonstrating that nondeterministic map iteration can change effective
configuration when plugins share keys.

Rule 6: Document configuration merge semantics explicitly
docs/dynamic-plugins.md[75-80]
internal/controller/devhubplugincatalog_controller.go[47-68]
pkg/catalog/processor.go[38-75]
pkg/catalog/processor.go[68-75]
docs/dynamic-plugins.md[71-80]
pkg/model/dynamic-plugins.go[139-156]
pkg/model/appconfig.go[67-83]
pkg/model/appconfig.go[177-195]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The documented alphabetical append order is not enforced: the controller does not sort catalog resources, and the processor loses insertion order by accumulating plugins in a Go map and using that map to determine output order. This makes precedence for overlapping `pluginConfig` values nondeterministic.

## Issue Context
Compliance rule 6 requires explicit merge semantics to match actual behavior. The controller should process catalogs alphabetically by resource name and preserve each catalog’s plugin order through patch generation; a map may still be used for duplicate detection, but it must not determine output ordering. Alternatively, if ordering has no supported meaning, revise the documentation to state that it is unspecified, and add tests for the selected contract.

## Fix Focus Areas
- docs/dynamic-plugins.md[75-80]
- internal/controller/devhubplugincatalog_controller.go[47-68]
- pkg/catalog/processor.go[38-75]
- pkg/catalog/processor_test.go[159-213]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Catalog merge semantics undocumented ✓ Resolved 📘 Rule violation ✧ Quality
Description
The documentation says catalog configurations are merged but does not explain that plugin lists are
concatenated in processing order and plugin objects are not shallow- or deep-merged. Users therefore
cannot predict the effective configuration when combining catalogs, violating the explicit
merge-semantics requirement.
Code

docs/dynamic-plugins.md[54]

+Plugin catalogs are defined using `DevHubPluginCatalog` resources in the operator namespace. The operator fetches catalogs from OCI registries, merges them, and makes the configuration available to Backstage instances.
Relevance

●●● Strong

Team consistently accepts docs clarifications on catalog/config merge behavior and semantics.

PR-#2717
PR-#3215

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 6 requires documentation of override-versus-merge behavior, merge depth, and
exceptions. The guide only states that catalogs are merged and mentions duplicate-name failure,
while Processor.merge appends complete plugin entries and rejects duplicate names without merging
nested values.

Rule 6: Document configuration merge semantics explicitly
docs/dynamic-plugins.md[54-75]
pkg/catalog/processor.go[121-146]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The plugin catalog guide says multiple catalogs are merged without defining the merge strategy.

## Issue Context
Document that plugin entries are appended in processing order, that plugin objects are not recursively merged, and that duplicate plugin names fail reconciliation rather than overriding an earlier entry.

## Fix Focus Areas
- docs/dynamic-plugins.md[54-54]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Ready update failures stall 🐞 Bug ☼ Reliability
Description
setAllConditions only logs status-update failures, so a successful catalog reconcile returns
without requeueing even when the final Ready=True update failed. Because Backstage waits for every
catalog to become Ready, that transient failure can leave all Backstage reconciliations blocked
indefinitely until another catalog generation event occurs.
Code

internal/controller/devhubplugincatalog_controller.go[R150-153]

+	for i := range catalogList.Items {
+		if err := r.setCondition(ctx, &catalogList.Items[i], status, reason, message); err != nil {
+			lg.Error(err, "Failed to set condition", "catalog", catalogList.Items[i].Name)
+		}
Relevance

●●● Strong

Ignoring status update errors risking stalled readiness matches accepted reliability fixes in
controller status handling.

PR-#3348

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The final Ready update is invoked immediately before a successful return, but setAllConditions
discards every error after logging it. The Backstage readiness gate only accepts persisted
Ready=True conditions and otherwise requeues itself without triggering a catalog reconcile.

internal/controller/devhubplugincatalog_controller.go[77-83]
internal/controller/devhubplugincatalog_controller.go[147-155]
internal/controller/devhubplugincatalog_controller.go[157-195]
internal/controller/backstage_controller.go[92-97]
internal/controller/backstage_controller.go[217-248]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Final Ready-condition update failures are swallowed, preventing controller-runtime from retrying and potentially leaving Backstage resources waiting forever.

## Issue Context
Return an aggregated error from condition updates and make successful reconciliation fail/requeue when Ready=True cannot be persisted.

## Fix Focus Areas
- internal/controller/devhubplugincatalog_controller.go[77-80]
- internal/controller/devhubplugincatalog_controller.go[147-155]
- internal/controller/backstage_controller.go[217-248]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (9)
14. Cache filter hides credentials 🐞 Bug ≡ Correctness
Description
When the supported ENABLE_CACHE_LABEL_FILTER option is enabled, the manager cache contains only
labeled Secrets and ConfigMaps, but the documented catalog credentials and CA objects are not
required or instructed to carry that label. The new cached Get calls consequently return NotFound
for valid private-registry configuration.
Code

internal/controller/devhubplugincatalog_controller.go[R86-89]

+			secret := &corev1.Secret{}
+			key := types.NamespacedName{Name: dhpc.Spec.Source.PullSecret.Name, Namespace: r.OperatorNamespace}
+			if err := r.Get(ctx, key, secret); err != nil {
+				return nil, fmt.Errorf("failed to get pull secret %q for catalog %q: %w", dhpc.Spec.Source.PullSecret.Name, dhpc.Name, err)
Relevance

●●● Strong

The cache filter is documented as breaking for unlabeled resources, and new credential examples omit
the required label.

PR-#2070

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The reconciler uses the manager client for referenced objects, while manager configuration can
filter all Secret and ConfigMap cache entries to rhdh.redhat.com/external-config=true; the new
documentation creates those objects without that label.

internal/controller/devhubplugincatalog_controller.go[84-106]
cmd/main.go[264-282]
docs/dynamic-plugins.md[76-127]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Catalog credential lookups are incompatible with the manager's optional Secret/ConfigMap cache filter.

## Issue Context
Use an uncached API reader for explicitly referenced objects, or define and document compatible labeling behavior.

## Fix Focus Areas
- internal/controller/devhubplugincatalog_controller.go[84-106]
- cmd/main.go[264-282]
- docs/dynamic-plugins.md[76-127]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


15. Later OCI layers ignored 🔗 Cross-repo conflict ≡ Correctness
Description
FetchContent extracts only layers[0], while the established rhdh-plugins catalog-index flow
processes every manifest layer. Consequently, a valid multi-layer catalog whose effective
dynamic-plugins.default.yaml is introduced or updated in a later layer can fail with a “catalog
file” not found error or cause the operator to use stale content.
Code

pkg/fetcher/oci.go[R153-162]

+	// 6. Extract content from first layer
+	layers, err := img.Layers()
+	if err != nil {
+		return nil, fmt.Errorf("failed to get layers: %w", err)
+	}
+	if len(layers) == 0 {
+		return nil, fmt.Errorf("artifact has no layers")
+	}
+
+	reader, err := layers[0].Uncompressed()
Relevance

●●● Strong

Selecting only the first layer conflicts with valid multi-layer OCI catalogs and the PR's catalog
extraction purpose.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The fetcher explicitly selects layer index zero, and the processor then requires the catalog file to
exist in that extracted content. In contrast, the established rhdh-plugins extraction implementation
searches across all layers, includes an all-layer fallback, and extracts manifest layers serially
into the same destination.

pkg/fetcher/oci.go[153-168]
pkg/catalog/processor.go[118-125]
hack/create-local-dynamic-plugins.sh[59-83]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/catalog-index.ts [80-91]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/catalog-index.ts [111-157]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The OCI fetcher reads only the first image layer, so valid multi-layer catalog images can lose the effective catalog file or use stale content. Update extraction to follow the established rhdh-plugins catalog-index behavior across all manifest layers.

## Issue Context
Apply image layers in manifest order into one destination so later files override earlier files consistently with normal layered-image behavior and the existing catalog-index consumer. Preserve OCI whiteout semantics, or otherwise search all layers consistently with the existing catalog extraction flow.

## Fix Focus Areas
- pkg/fetcher/oci.go[153-181]
- pkg/fetcher/extract.go[49-97]
- pkg/fetcher/oci_test.go[16-295]
- hack/create-local-dynamic-plugins.sh[59-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


16. Referenced CRD manifest missing ✓ Resolved 📘 Rule violation ✧ Quality
Description
The RHDH manifest bundle now references bases/rhdh.redhat.com_devhubplugincatalogs.yaml, but that
file is absent from the PR branch. As a result, the example/install Kustomization cannot build or be
applied as a complete resource set.
Code

config/crd/kustomization.yaml[6]

+- bases/rhdh.redhat.com_devhubplugincatalogs.yaml
Relevance

●●● Strong

The added Kustomize resource reference points to an absent CRD file, causing deterministic build
failure.

PR-#1406

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 1 requires custom-resource examples to include their CRD definition or a clear reference to an
existing CRD manifest. The changed Kustomization adds a reference to
bases/rhdh.redhat.com_devhubplugincatalogs.yaml, while inspection of config/crd/bases confirms
that no matching file exists in the PR branch.

Rule 1: Include all dependent Kubernetes resources in example manifests
config/crd/kustomization.yaml[4-7]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The CRD Kustomization references a `DevHubPluginCatalog` CRD manifest that is not present, so the manifest bundle cannot be built or applied successfully.

## Issue Context
The RHDH profile includes `../../crd` and also includes a `DevHubPluginCatalog` custom resource. Compliance rule 1 requires the referenced CRD definition to be included in the same bundle.

## Fix Focus Areas
- config/crd/kustomization.yaml[6-6]
- config/crd/bases/rhdh.redhat.com_devhubplugincatalogs.yaml[1-1]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


17. Credential changes not watched 🐞 Bug ☼ Reliability
Description
The reconciler reads referenced pull Secrets and CA ConfigMaps but watches only
DevHubPluginCatalog objects. Correcting or rotating credentials or certificates does not trigger a
re-fetch, leaving failed or stale catalog output until an unrelated catalog event occurs.
Code

internal/controller/devhubplugincatalog_controller.go[R129-133]

+func (r *DevHubPluginCatalogReconciler) SetupWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&api.DevHubPluginCatalog{}).
+		Named("devhubplugincatalog").
+		Complete(r)
Relevance

●●● Strong

Referenced Secrets and ConfigMaps affect reconciliation output, so failing to watch changes leaves
credentials and catalogs stale.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Secrets and CA ConfigMaps are consumed during input construction, but controller setup registers
only the primary catalog resource.

internal/controller/devhubplugincatalog_controller.go[84-106]
internal/controller/devhubplugincatalog_controller.go[128-133]
api/v1alpha5/devhubplugincatalog_types.go[22-29]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Changes to referenced Secrets and ConfigMaps do not enqueue catalog reconciliation.

## Issue Context
Add indexed/mapped watches that enqueue affected catalogs while avoiding reconciliation for unrelated cluster objects.

## Fix Focus Areas
- internal/controller/devhubplugincatalog_controller.go[84-106]
- internal/controller/devhubplugincatalog_controller.go[128-133]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


18. Catalog includes are discarded 🔗 Cross-repo conflict ≡ Correctness
Description
The merge path unmarshals the established dynamic-plugin schema but returns only Plugins, silently
dropping every catalog's includes. rhdh-plugins supports and resolves those include files, so
compliant catalog configurations using them lose plugins when consumed by this controller.
Code

pkg/catalog/processor.go[R151-156]

+			allPlugins = append(allPlugins, plugin)
+			seen[name] = plugin.Package
+		}
+	}
+
+	return yaml.Marshal(model.DynaPluginsConfig{Plugins: allPlugins})
Relevance

●●● Strong

Accepted merge precedent favors preserving configuration fields; dropping includes silently loses
supported catalog content.

PR-#1486

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR's merge result constructs a new configuration containing only Plugins. The cross-repo
schema explicitly includes includes, and the installer resolves, reads, validates, and merges each
included plugin list.

pkg/catalog/processor.go[134-156]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/types.ts [68-77]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/installer.ts [306-331]
External repo: redhat-developer/rhdh-plugins, workspaces/install-dynamic-plugins/packages/install-dynamic-plugins/src/installer.ts [370-382]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The catalog processor drops top-level `includes` from catalog configurations even though they are part of the rhdh-plugins dynamic-plugin contract.

## Issue Context
Because fetched artifacts are temporary, resolve relative include files from each extracted catalog before cleanup and flatten their plugin entries into the merged output, or otherwise preserve an installer-accessible include contract.

## Fix Focus Areas
- pkg/catalog/processor.go[97-128]
- pkg/catalog/processor.go[131-156]
- pkg/catalog/processor_test.go[20-176]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


19. CA overrides insecure mode 🐞 Bug ≡ Correctness
Description
When both skipTLSVerify and certificateAuthority are configured, processor option order applies
WithCACert last and replaces the transport created by Wit

[Comment truncated to fit github's 65,536-char limit.]

Comment thread pkg/fetcher/oci.go Resolved
Comment thread dist/rhdh/install.yaml Resolved
Comment thread pkg/model/dynamic-plugins.go Resolved
Comment thread pkg/catalog/processor.go Resolved
@rhdh-qodo-merge rhdh-qodo-merge Bot added documentation Improvements or additions to documentation enhancement New feature or request Tests labels Aug 25, 2026
@gazarenkov

Copy link
Copy Markdown
Member Author

/agentic_review

Comment thread cmd/main.go Resolved
Comment thread Makefile Resolved
Comment thread pkg/catalog/processor.go Resolved
Comment thread pkg/catalog/processor.go Resolved
Comment thread pkg/fetcher/oci.go Resolved
Comment thread Makefile Resolved
Comment thread config/samples/devhubplugincatalog.yaml Resolved
Comment thread pkg/fetcher/oci.go Resolved
# Conflicts:
#	bundle/backstage.io/manifests/backstage-operator.clusterserviceversion.yaml
#	bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml
@rm3l

rm3l commented Sep 8, 2026

Copy link
Copy Markdown
Member

/build-images

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

✅ PR images built successfully!

Images are available for testing:

  1. Operator: quay.io/rhdh-community/operator:2.0.0-pr-3422-c99fdbc
  2. Bundle: quay.io/rhdh-community/operator-bundle:2.0.0-pr-3422-c99fdbc
  3. Catalog: quay.io/rhdh-community/operator-catalog:2.0.0-pr-3422-c99fdbc

Also available with PR number tag:

  • quay.io/rhdh-community/operator:2.0.0-pr-3422
  • quay.io/rhdh-community/operator-bundle:2.0.0-pr-3422
  • quay.io/rhdh-community/operator-catalog:2.0.0-pr-3422

Triggered by @rm3l

@openshift-ci openshift-ci Bot added the lgtm label Sep 8, 2026
@rm3l

rm3l commented Sep 8, 2026

Copy link
Copy Markdown
Member

Approved, but the PR branch should be rebased to fix the conflicts.

# Conflicts:
#	Makefile
#	bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml
#	config/profile/rhdh/kustomization.yaml
#	dist/rhdh/install.yaml
@openshift-ci openshift-ci Bot removed the lgtm label Sep 8, 2026
@openshift-ci

openshift-ci Bot commented Sep 8, 2026

Copy link
Copy Markdown

New changes are detected. LGTM label has been removed.

@sonarqubecloud

sonarqubecloud Bot commented Sep 8, 2026

Copy link
Copy Markdown

@gazarenkov
gazarenkov merged commit fe0ada5 into redhat-developer:main Sep 8, 2026
14 of 15 checks passed
@rhdh-qodo-merge rhdh-qodo-merge Bot mentioned this pull request Sep 9, 2026
2 tasks done
@gazarenkov
gazarenkov deleted the devhub-plugin-catalog branch October 1, 2026 09:00
@nickboldt

nickboldt commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

FYI breaking downstream. Needs to be disabled in 2.1 as it's only DP and we can't refer to quay.io images in prod images

See https://redhat.atlassian.net/browse/RHDHBUGS-3899?focusedCommentId=18746047

@nickboldt

Copy link
Copy Markdown
Member

Aaand it's disabled. Builds are working again.

Should be able to remove this workaround from main branch once there's a productized image https://gitlab.cee.redhat.com/rhidp/rhdh/-/blob/main/build/ci/sync-midstream.sh?ref_type=heads#L759-762

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants