Skip to content

feat: RHIDP-9764-Add-catalog-index-support#280

Merged
rm3l merged 19 commits intoredhat-developer:mainfrom
Fortune-Ndlovu:RHIDP-9764-Add-catalog-index-support
Dec 17, 2025
Merged

feat: RHIDP-9764-Add-catalog-index-support#280
rm3l merged 19 commits intoredhat-developer:mainfrom
Fortune-Ndlovu:RHIDP-9764-Add-catalog-index-support

Conversation

@Fortune-Ndlovu
Copy link
Copy Markdown
Member

@Fortune-Ndlovu Fortune-Ndlovu commented Dec 8, 2025

Description of the change

This PR adds support for consuming the plugin catalog index OCI artifact in the Helm chart. The install-dynamic-plugins.py script can now pull and extract the catalog index image (RHIDP-9760). This PR adds:
global.catalogIndex.image configuration (with registry, repository, and tag fields) CATALOG_INDEX_IMAGE environment variable to the install-dynamic-plugins init container

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

How to test changes / Special notes to the reviewer

Checklist

  • For each Chart updated, version bumped in the corresponding Chart.yaml according to Semantic Versioning.
  • For each Chart updated, variables are documented in the values.yaml and added to the corresponding README.md. The pre-commit utility can be used to generate the necessary content. Use pre-commit run -a to apply changes. The pre-commit Workflow will do this automatically for you if needed.
  • JSON Schema template updated and re-generated the raw schema via the pre-commit hook.
  • Tests pass using the Chart Testing tool and the ct lint command.
  • If you updated the orchestrator-infra chart, make sure the versions of the Knative CRDs are aligned with the versions of the CRDs installed by the OpenShift Serverless operators declared in the values.yaml file. See Installing Knative Eventing and Knative Serving CRDs for more details.

Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
@rhdh-qodo-merge
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ❌

RHIDP-9764 - Partially compliant

Compliant requirements:

  • Set CATALOG_INDEX_IMAGE environment variable in install-dynamic-plugins init container
  • Add catalogIndex configuration in values.yaml with image field
  • Update values.schema.json to validate catalogIndex.image
  • Allow users to override catalogIndex.image value
  • Ensure Helm chart renders correctly with default values

Non-compliant requirements:

  • Add catalogIndex configuration in values.yaml with enabled field

Requires further human verification:

  • Ensure Helm chart renders correctly with default values (helm template/lint)

RHIDP-9760 - Not compliant

Non-compliant requirements:

  • Update install-dynamic-plugins.py to pull catalog index when CATALOG_INDEX_IMAGE is set
  • Extract catalog index directly to /dynamic-plugins-root volume

Requires further human verification:

  • Validate install-dynamic-plugins.py behavior at runtime with an actual catalog index image
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Misconfiguration

The ticket mentions an 'enabled' field for catalogIndex, but only 'image' is introduced. Confirm whether an 'enabled' toggle is required and, if so, add it to values, schema, and README to match acceptance criteria.

# -- Catalog index configuration for automatic plugin discovery.
# The `install-dynamic-plugins.py` script will pull this image if the `CATALOG_INDEX_IMAGE` environment variable is set, extract `dynamic-plugins.default.yaml`,
# and write it to `dynamic-plugins-root` volume mount.
catalogIndex:
  image: "quay.io/rhdh/plugin-catalog-index:1.9"
📚 Focus areas based on broader codebase context

Template Value Leakage

The JSON schema includes Helm template expressions in default values (e.g., image: "{{ include "backstage.image" . }}" and env value: "{{ .Values.global.dynamic.catalogIndex.image }}"). JSON schema files should contain plain JSON defaults, not Go-templating. Move templated defaults to the tmpl schema and keep the generated schema free of templates. (Ref 2, Ref 5)

"initContainers": {
    "default": [
        {
            "command": [
                "./install-dynamic-plugins.sh",
                "/dynamic-plugins-root"
            ],
            "env": [
                {
                    "name": "NPM_CONFIG_USERCONFIG",
                    "value": "/opt/app-root/src/.npmrc.dynamic-plugins"
                },
                {
                    "name": "MAX_ENTRY_SIZE",
                    "value": "30000000"
                },
                {
                    "name": "CATALOG_INDEX_IMAGE",
                    "value": "{{ .Values.global.dynamic.catalogIndex.image }}"
                }

Reference reasoning: Other schema templates place templated strings only in the .tmpl.json file and keep the generated values.schema.json with plain JSON types and defaults. The existing tmpl schema for Backstage shows this separation, indicating templating belongs in the tmpl file, not in the finalized JSON schema.

📄 References
  1. redhat-developer/rhdh-chart/charts/orchestrator-infra/values.schema.json [1-133]
  2. redhat-developer/rhdh-chart/charts/backstage/values.schema.tmpl.json [199-305]
  3. redhat-developer/rhdh-chart/charts/backstage/values.schema.tmpl.json [419-423]
  4. redhat-developer/rhdh-chart/charts/orchestrator-infra/values.schema.tmpl.json [1-136]
  5. redhat-developer/rhdh-chart/charts/backstage/values.schema.tmpl.json [109-199]
  6. redhat-developer/rhdh-chart/charts/backstage/values.schema.tmpl.json [306-418]

@rhdh-qodo-merge rhdh-qodo-merge Bot added the enhancement New feature or request label Dec 8, 2025
@rhdh-qodo-merge
Copy link
Copy Markdown

PR Type

Enhancement


Description

  • Add catalog index image configuration support to enable automatic plugin discovery

  • Introduce global.dynamic.catalogIndex.image configuration parameter with default value

  • Add CATALOG_INDEX_IMAGE environment variable to install-dynamic-plugins init container

  • Update JSON schema and documentation to reflect new catalog index configuration


File Walkthrough

Relevant files
Enhancement
values.yaml
Add catalogIndex configuration and environment variable   

charts/backstage/values.yaml

  • Add catalogIndex configuration block under global.dynamic with image
    property
  • Set default catalog index image to
    quay.io/rhdh/plugin-catalog-index:1.9
  • Add CATALOG_INDEX_IMAGE environment variable to
    install-dynamic-plugins init container
  • Include documentation comments explaining catalog index functionality
+8/-0     
Configuration changes
values.schema.tmpl.json
Add catalogIndex schema definition to template                     

charts/backstage/values.schema.tmpl.json

  • Add catalogIndex object schema definition under global.dynamic
    properties
  • Define image property with string type and default value
  • Set additionalProperties to false for strict schema validation
  • Include title and description for catalog index configuration
+12/-0   
values.schema.json
Add catalogIndex schema and environment variable                 

charts/backstage/values.schema.json

  • Add catalogIndex object schema with image property definition
  • Set default image value to quay.io/rhdh/plugin-catalog-index:1.9
  • Add CATALOG_INDEX_IMAGE environment variable to init container schema
  • Include schema validation with additionalProperties: false
+16/-0   
Documentation
README.md
Document catalogIndex configuration parameter                       

charts/backstage/README.md

  • Document new global.dynamic.catalogIndex configuration parameter
  • Explain that install-dynamic-plugins.py script uses this image for
    plugin discovery
  • Describe extraction of dynamic-plugins.default.yaml from catalog index
    image
  • Provide default value and object type information
+1/-0     

@rhdh-qodo-merge
Copy link
Copy Markdown

rhdh-qodo-merge Bot commented Dec 8, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Remove Helm templating from values file
Suggestion Impact:The CATALOG_INDEX_IMAGE environment variable was removed from values.yaml and reintroduced in the initContainer env within the template, sourcing from a new top-level values key (pluginCatalogIndex.image), thus moving templating logic out of values.yaml as suggested.

code diff:

+# -- Catalog index configuration for automatic plugin discovery.
+# The `install-dynamic-plugins.py` script pulls this image if the `CATALOG_INDEX_IMAGE` environment variable is set.
+# The `dynamic-plugins.default.yaml` file will be extracted and written to `dynamic-plugins-root` volume mount.
+pluginCatalogIndex:
+  # -- OCI image for the plugin catalog index. Set to empty string to disable.
+  image: "quay.io/rhdh/plugin-catalog-index:1.9"
+
 global:
   dynamic:
     # -- Array of YAML files listing dynamic plugins to include with those listed in the `plugins` field.
     # Relative paths are resolved from the working directory of the initContainer that will install the plugins (`/opt/app-root/src`).
     includes:
-      # -- List of dynamic plugins included inside the `janus-idp/backstage-showcase` container image, some of which are disabled by default.
-      # This file ONLY works with the `janus-idp/backstage-showcase` container image.
-      - "dynamic-plugins.default.yaml"
-
+    # -- List of dynamic plugins included inside the `janus-idp/backstage-showcase` container image, some of which are disabled by default.
+    # This file ONLY works with the `janus-idp/backstage-showcase` container image.
+    - "dynamic-plugins.default.yaml"
     # -- List of dynamic plugins, possibly overriding the plugins listed in `includes` files.
     # Every item defines the plugin `package` as a [NPM package spec](https://docs.npmjs.com/cli/v10/using-npm/package-spec),
     # an optional `pluginConfig` with plugin-specific backstage configuration, and an optional `disabled` flag to disable/enable a plugin
     # listed in `includes` files. It also includes an `integrity` field that is used to verify the plugin package [integrity](https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description).
     plugins: []
-
-    # -- Catalog index configuration for automatic plugin discovery.
-    # The `install-dynamic-plugins.py` script will pull this image if the `CATALOG_INDEX_IMAGE` environment variable is set, extract `dynamic-plugins.default.yaml`,
-    # and write it to `dynamic-plugins-root` volume mount.
-    catalogIndex:
-      image: "quay.io/rhdh/plugin-catalog-index:1.9"
 
   # -- Shorthand for users who do not want to specify a custom HOSTNAME. Used ONLY with the DEFAULT upstream.backstage.appConfig value and with OCP Route enabled.
   clusterRouterBase: "apps.example.com"
@@ -45,7 +46,7 @@
       registry: quay.io
       repository: rhdh-community/rhdh
       tag: next
-      pullPolicy: IfNotPresent
+      pullPolicy: ""
     command: []
     # FIXME (tumido): USE POSTGRES_PASSWORD and POSTGRES_USER instead of POSTGRES_ADMIN_PASSWORD
     # This is a hack. In {fedora,rhel}/postgresql images, regular user is forbidden
@@ -70,15 +71,15 @@
             user: postgres
         auth:
           externalAccess:
-            - type: legacy
-              options:
-                subject: legacy-default-config
-                secret: ${BACKEND_SECRET}
+          - type: legacy
+            options:
+              subject: legacy-default-config
+              secret: ${BACKEND_SECRET}
     containerSecurityContext:
       readOnlyRootFilesystem: true
       allowPrivilegeEscalation: false
       capabilities:
-        drop: ["ALL"]
+        drop: [ "ALL" ]
       runAsNonRoot: true
       seccompProfile:
         type: "RuntimeDefault"
@@ -131,121 +132,120 @@
       successThreshold: 1
       timeoutSeconds: 4
     extraEnvVars:
-      - name: BACKEND_SECRET
-        valueFrom:
-          secretKeyRef:
-            key: backend-secret
-            name: '{{ include "janus-idp.backend-secret-name" $ }}'
-      - name: POSTGRESQL_ADMIN_PASSWORD
-        valueFrom:
-          secretKeyRef:
-            key: postgres-password
-            name: '{{- include "janus-idp.postgresql.secretName" . }}'
+    - name: BACKEND_SECRET
+      valueFrom:
+        secretKeyRef:
+          key: backend-secret
+          name: '{{ include "janus-idp.backend-secret-name" $ }}'
+    - name: POSTGRESQL_ADMIN_PASSWORD
+      valueFrom:
+        secretKeyRef:
+          key: postgres-password
+          name: '{{- include "janus-idp.postgresql.secretName" . }}'
     args:
-      # This additional `app-config`` file is generated by the initContainer below, and contains the merged configuration of installed dynamic plugins.
-      - "--config"
-      - dynamic-plugins-root/app-config.dynamic-plugins.yaml
+    # This additional `app-config`` file is generated by the initContainer below, and contains the merged configuration of installed dynamic plugins.
+    - "--config"
+    - dynamic-plugins-root/app-config.dynamic-plugins.yaml
     extraVolumeMounts:
-      # The initContainer below will install dynamic plugins in this volume mount.
-      - name: dynamic-plugins-root
-        mountPath: /opt/app-root/src/dynamic-plugins-root
+    # The initContainer below will install dynamic plugins in this volume mount.
+    - name: dynamic-plugins-root
+      mountPath: /opt/app-root/src/dynamic-plugins-root
+    - name: temp
+      mountPath: /tmp
+    extraVolumes:
+    # -- Ephemeral volume that will contain the dynamic plugins installed by the initContainer below at start.
+    - name: dynamic-plugins-root
+      ephemeral:
+        volumeClaimTemplate:
+          spec:
+            accessModes:
+            - ReadWriteOnce
+            resources:
+              requests:
+                # -- Size of the volume that will contain the dynamic plugins. It should be large enough to contain all the plugins.
+                storage: 5Gi
+    # Volume that will expose the `dynamic-plugins.yaml` file from the `dynamic-plugins` config map.
+    # The `dynamic-plugins` config map is created by the helm chart from the content of the `global.dynamic` field.
+    - name: dynamic-plugins
+      configMap:
+        defaultMode: 420
+        name: '{{ printf "%s-dynamic-plugins" .Release.Name }}'
+        optional: true
+    # Optional volume that allows exposing the `.npmrc` file (through a `dynamic-plugins-npmrc` secret)
+    # to be used when running `npm pack` during the dynamic plugins installation by the initContainer.
+    - name: dynamic-plugins-npmrc
+      secret:
+        defaultMode: 420
+        optional: true
+        secretName: '{{ printf "%s-dynamic-plugins-npmrc" .Release.Name }}'
+    # Optional volume that allows adding a container registry `auth.json` file (through a `dynamic-plugins-registry-auth` secret)
+    # to be used when installing plugins from secure container registries during the dynamic plugins installation by the initContainer.
+    - name: dynamic-plugins-registry-auth
+      secret:
+        defaultMode: 416
+        optional: true
+        secretName: '{{ printf "%s-dynamic-plugins-registry-auth" .Release.Name }}'
+    - name: npmcacache
+      emptyDir: {}
+    - name: temp
+      emptyDir: {}
+    initContainers:
+    - name: install-dynamic-plugins
+      resources:
+        requests:
+          cpu: 250m
+          memory: 256Mi
+        limits:
+          cpu: 1000m
+          memory: 2.5Gi
+          ephemeral-storage: 5Gi
+      securityContext:
+        readOnlyRootFilesystem: true
+        allowPrivilegeEscalation: false
+        capabilities:
+          drop: [ "ALL" ]
+        runAsNonRoot: true
+        seccompProfile:
+          type: "RuntimeDefault"
+      # -- Image used by the initContainer to install dynamic plugins into the `dynamic-plugins-root` volume mount.
+      # It could be replaced by a custom image based on this one.
+      # @default -- `quay.io/janus-idp/backstage-showcase:latest`
+      image: '{{ include "backstage.image" . }}'
+      command:
+      - ./install-dynamic-plugins.sh
+      - /dynamic-plugins-root
+      env:
+      - name: NPM_CONFIG_USERCONFIG
+        value: /opt/app-root/src/.npmrc.dynamic-plugins
+        # This following variable is required for orchestrator to startup properly.
+      - name: MAX_ENTRY_SIZE
+        value: "30000000"
+      - name: CATALOG_INDEX_IMAGE
+        value: '{{ .Values.pluginCatalogIndex.image }}'
+      imagePullPolicy: ""
+      volumeMounts:
+      - mountPath: /dynamic-plugins-root
+        name: dynamic-plugins-root
+      - mountPath: /opt/app-root/src/dynamic-plugins.yaml
+        name: dynamic-plugins
+        readOnly: true
+        subPath: dynamic-plugins.yaml
+      - mountPath: /opt/app-root/src/.npmrc.dynamic-plugins
+        name: dynamic-plugins-npmrc
+        readOnly: true
+        subPath: .npmrc
+      - mountPath: /opt/app-root/src/.config/containers

Remove the CATALOG_INDEX_IMAGE environment variable from values.yaml as it
incorrectly contains Helm templating. This logic should be moved to a Kubernetes
manifest template file.

charts/backstage/values.yaml [220-223]

 - name: MAX_ENTRY_SIZE
   value: "30000000"
-- name: CATALOG_INDEX_IMAGE
-  value: '{{ .Values.global.dynamic.catalogIndex.image }}'

[Suggestion processed]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical Helm chart authoring error where template logic is placed in values.yaml, which would cause the chart to fail.

High
Remove invalid Helm templating from schema

Remove the CATALOG_INDEX_IMAGE environment variable definition from
values.schema.json because it contains Helm templating syntax, which makes the
JSON schema invalid.

charts/backstage/values.schema.json [4631-4638]

 {
     "name": "MAX_ENTRY_SIZE",
     "value": "30000000"
-},
-{
-    "name": "CATALOG_INDEX_IMAGE",
-    "value": "{{ .Values.global.dynamic.catalogIndex.image }}"
 }
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly points out that including Helm templating in values.schema.json makes it invalid JSON, which will break schema validation and tooling.

High
High-level
Add a boolean flag to disable the catalog index feature

Add a boolean enabled flag to the catalogIndex configuration to allow users to
disable the feature. This change would align the implementation with the
acceptance criteria of the associated ticket.

Examples:

charts/backstage/values.yaml [20-21]
    catalogIndex:
      image: "quay.io/rhdh/plugin-catalog-index:1.9"
charts/backstage/values.schema.tmpl.json [76-87]
                        "catalogIndex": {
                            "title": "Catalog index configuration for automatic plugin discovery",
                            "type": "object",
                            "additionalProperties": false,
                            "properties": {
                                "image": {
                                    "title": "Catalog index OCI image reference",
                                    "type": "string",
                                    "default": "quay.io/rhdh/plugin-catalog-index:1.9"
                                }

 ... (clipped 2 lines)

Solution Walkthrough:

Before:

# charts/backstage/values.yaml
global:
  dynamic:
    catalogIndex:
      image: "quay.io/rhdh/plugin-catalog-index:1.9"

# In the deployment template (inferred from values.yaml)
...
  initContainers:
  - name: install-dynamic-plugins
    env:
    - name: CATALOG_INDEX_IMAGE
      value: '{{ .Values.global.dynamic.catalogIndex.image }}'

After:

# charts/backstage/values.yaml
global:
  dynamic:
    catalogIndex:
      enabled: true
      image: "quay.io/rhdh/plugin-catalog-index:1.9"

# In the deployment template (inferred from values.yaml)
...
  initContainers:
  - name: install-dynamic-plugins
    env:
    {{- if .Values.global.dynamic.catalogIndex.enabled }}
    - name: CATALOG_INDEX_IMAGE
      value: '{{ .Values.global.dynamic.catalogIndex.image }}'
    {{- end }}
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the PR omits an enabled flag, which is explicitly required by the acceptance criteria of the linked ticket RHIDP-9764, making this a significant feature implementation gap.

Medium
  • Update

@Fortune-Ndlovu Fortune-Ndlovu self-assigned this Dec 8, 2025
Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
Co-authored-by: Fortune-Ndlovu <Fortune-Ndlovu@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

⚠️ Files changed after running the pre-commit hooks

Those changes should have been pushed automatically to your PR branch.

NOTE: If the PR checks are stuck after this additional commit, manually close the PR and immediately reopen it to trigger the checks again.

… default behavior, and configuration options

Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
Copy link
Copy Markdown
Member

@deerskindoll deerskindoll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a couple of small tweaks based on google developer documentation style guide

Comment thread charts/backstage/README.md Outdated
Comment thread charts/backstage/README.md Outdated
Comment thread charts/backstage/README.md.gotmpl Outdated
Comment thread charts/backstage/values.yaml Outdated
Comment thread docs/catalog-index-configuration.md Outdated
Comment thread docs/catalog-index-configuration.md Outdated
Comment thread docs/catalog-index-configuration.md Outdated
Comment thread docs/catalog-index-configuration.md Outdated
Comment thread charts/backstage/values.yaml Outdated
Comment thread charts/backstage/values.yaml Outdated
Comment thread docs/catalog-index-configuration.md Outdated
Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
…ndex at the root level

Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
Comment thread charts/backstage/values.yaml Outdated
Comment thread docs/catalog-index-configuration.md Outdated
Copy link
Copy Markdown
Member

@nickboldt nickboldt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see inline comments. in particular:

  • need more consistent approach to referencing the new index image in both helm and operator;
  • don't want to tell people how to disable the index and end up with no DPDY file for their dynamic plugin defaults

…el and Update the CATALOG_INDEX_IMAGE environment variable in the install-dynamic-plugins init container to construct the image reference. Also simplify the catalog index documentation by removing duplicated private registry authentication instructions and linking to the official Red Hat Developer Hub documentation instead

Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
…lugins.default.yaml file is being moved from the RHDH container to the catalog index image

Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
@Fortune-Ndlovu
Copy link
Copy Markdown
Member Author

cc/ @rm3l and @nickboldt for review please

Comment thread charts/backstage/values.yaml Outdated
Comment thread charts/backstage/values.yaml Outdated
Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
Comment thread charts/backstage/values.yaml
…ma.tmpl.json. This is the source file that pre-commit uses to generate values.schema.json.

Signed-off-by: Fortune Ndlovu <fndlovu@redhat.com>
@sonarqubecloud
Copy link
Copy Markdown

@Fortune-Ndlovu Fortune-Ndlovu requested a review from rm3l December 17, 2025 12:12
Copy link
Copy Markdown
Member

@rm3l rm3l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@openshift-ci openshift-ci Bot added the lgtm label Dec 17, 2025
@rm3l rm3l merged commit 353db4e into redhat-developer:main Dec 17, 2025
7 checks passed
@Fortune-Ndlovu Fortune-Ndlovu deleted the RHIDP-9764-Add-catalog-index-support branch December 17, 2025 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants