From c860d35eeba19456c5872407487a220c465927c4 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Tue, 23 Jun 2026 04:16:59 +0100 Subject: [PATCH 01/30] RHIDP-14785: Add OLM v1 path to prepare-restricted-environment.sh Add OLM version detection and branching to the airgap script so it generates the correct manifests for both OLM v0 and OLM v1 clusters. When OLM v1 is detected (or forced via --olm-version), the script produces ClusterCatalog, ClusterExtension, ServiceAccount, and ClusterRoleBinding instead of CatalogSource, OperatorGroup, and Subscription. The oc-mirror path converts CatalogSource output to ClusterCatalog. Export-only mode generates both manifest sets. Signed-off-by: Fortune-Ndlovu Assisted-by: Claude (Opus 4.6) --- .../scripts/prepare-restricted-environment.sh | 343 ++++++++++++++++-- 1 file changed, 310 insertions(+), 33 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index c0c7a1c11..581dc7586 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -17,6 +17,8 @@ IS_HOSTED_CONTROL_PLANE="" NAMESPACE_OPERATOR="rhdh-operator" INDEX_IMAGE="registry.redhat.io/redhat/redhat-operator-index:v4.18" FILTERED_VERSIONS=(*) +OLM_VERSION="auto" +RESOLVED_OLM_VERSION="" # assume mikefarah version of yq is already available on the path; if 1, then install the version shown INSTALL_YQ=0 @@ -112,6 +114,10 @@ Options: --to-registry together. --oc-mirror-path : Path to the oc-mirror binary (default: 'oc-mirror'). --oc-mirror-flags : Additional flags to pass to all oc-mirror commands. + --olm-version v0|v1|auto : Force OLM version for catalog/operator resources (default: auto-detect). + 'auto' detects OLM v1 by checking for the ClusterExtension CRD on the cluster. + When v1 is detected or forced, generates ClusterCatalog + ClusterExtension + instead of CatalogSource + Subscription. --install-yq : Install yq $YQ_VERSION from https://github.com/mikefarah/yq (not the jq python wrapper) Examples: @@ -265,7 +271,16 @@ while [[ "$#" -gt 0 ]]; do OC_MIRROR_FLAGS="$2" shift 1 ;; - '--install-yq') + '--olm-version') + if [[ "$2" != "v0" && "$2" != "v1" && "$2" != "auto" ]]; then + errorf "Unknown OLM version: $2. Must be v0, v1, or auto." + usage + exit 1 + fi + OLM_VERSION="$2" + shift 1 + ;; + '--install-yq') INSTALL_YQ=1 ;; '-h' | '--help') usage @@ -327,6 +342,82 @@ function invoke_cluster_cli() { fi } +function detect_olm_v1() { + invoke_cluster_cli get crd clusterextensions.olm.operatorframework.io &>/dev/null +} + +function resolve_olm_version() { + set -euo pipefail + + if [[ "${IS_OPENSHIFT}" != "true" ]]; then + if [[ "${OLM_VERSION}" == "v1" ]]; then + warnf "OLM v1 is not supported on Kubernetes clusters; falling back to v0" + fi + RESOLVED_OLM_VERSION="v0" + return + fi + + if [[ "${OLM_VERSION}" == "v0" ]]; then + RESOLVED_OLM_VERSION="v0" + infof "Using OLM v0 (forced via --olm-version)" + elif [[ "${OLM_VERSION}" == "v1" ]]; then + if ! detect_olm_v1; then + errorf "OLM v1 requested but ClusterExtension CRD not found on this cluster" + exit 1 + fi + RESOLVED_OLM_VERSION="v1" + infof "Using OLM v1 (forced via --olm-version)" + else + # auto-detect + if detect_olm_v1; then + RESOLVED_OLM_VERSION="v1" + infof "Auto-detected OLM v1 (ClusterExtension CRD found)" + else + RESOLVED_OLM_VERSION="v0" + infof "Auto-detected OLM v0 (ClusterExtension CRD not found)" + fi + fi +} + +function prepare_olm_v1_secrets() { + set -euo pipefail + + if [[ "${RESOLVED_OLM_VERSION}" != "v1" ]]; then + return + fi + + NAMESPACE_CATALOGD=$(invoke_cluster_cli get deployment -A -l 'app.kubernetes.io/name=catalogd' \ + -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true) + if [[ -z "${NAMESPACE_CATALOGD}" ]]; then + NAMESPACE_CATALOGD="openshift-catalogd" + fi + debugf "Using catalogd namespace: ${NAMESPACE_CATALOGD}" + + if ! invoke_cluster_cli get namespace "${NAMESPACE_CATALOGD}" &>/dev/null; then + errorf "Catalogd namespace '${NAMESPACE_CATALOGD}' not found. Is OLM v1 installed correctly?" + exit 1 + fi + + for ns in "${NAMESPACE_CATALOGD}" "${NAMESPACE_OPERATOR}"; do + if invoke_cluster_cli -n "${ns}" get secret internal-reg-ext-auth-for-rhdh &>/dev/null; then + invoke_cluster_cli -n "${ns}" delete secret internal-reg-ext-auth-for-rhdh >&2 + fi + invoke_cluster_cli -n "${ns}" create secret docker-registry internal-reg-ext-auth-for-rhdh \ + --docker-server="$(buildRegistryUrl)" \ + --docker-username=kubeadmin \ + --docker-password="$(oc whoami -t)" \ + --docker-email="admin@internal-registry-ext.example.com" >&2 + if invoke_cluster_cli -n "${ns}" get secret internal-reg-auth-for-rhdh &>/dev/null; then + invoke_cluster_cli -n "${ns}" delete secret internal-reg-auth-for-rhdh >&2 + fi + invoke_cluster_cli -n "${ns}" create secret docker-registry internal-reg-auth-for-rhdh \ + --docker-server="image-registry.openshift-image-registry.svc:5000" \ + --docker-username=kubeadmin \ + --docker-password="$(oc whoami -t)" \ + --docker-email="admin@internal-registry.example.com" >&2 + done +} + ########################################################################################## # Script start ########################################################################################## @@ -875,6 +966,18 @@ if [[ "${IS_OPENSHIFT}" = "true" && "${TO_REGISTRY}" = "OCP_INTERNAL" ]]; then ocp_prepare_internal_registry fi +if [[ -n "${TO_REGISTRY}" ]]; then + resolve_olm_version + if [[ "${IS_OPENSHIFT}" = "true" && "${TO_REGISTRY}" = "OCP_INTERNAL" && "${RESOLVED_OLM_VERSION}" == "v1" ]]; then + prepare_olm_v1_secrets + fi +else + if [[ "${OLM_VERSION}" != "auto" ]]; then + RESOLVED_OLM_VERSION="${OLM_VERSION}" + else + RESOLVED_OLM_VERSION="v0" + fi +fi manifestsTargetDir="${TMPDIR}" if [[ -n "${FROM_DIR}" ]]; then @@ -1030,13 +1133,30 @@ EOF # oc-mirror v2 generates files with pattern: cs-*.yaml for manifest in "${clusterResourcesDir}"/cs-*.yaml "${clusterResourcesDir}"/catalogSource*.yaml; do if [[ -f "${manifest}" ]]; then - debugf "Processing CatalogSource: ${manifest}" - # Replace some metadata and add the default list of secrets - "$YQ" -i '.metadata.name = "rhdh-catalog"' "${manifest}" - "$YQ" -i '.spec.displayName = "Red Hat Developer Hub Catalog (Airgapped)"' "${manifest}" - "$YQ" -i '.spec.secrets = (.spec.secrets // []) + ["internal-reg-auth-for-rhdh", "internal-reg-ext-auth-for-rhdh"]' "${manifest}" - "$YQ" -i '.spec.image |= sub("default-route-openshift-image-registry\.apps\.[^/]+", "image-registry.openshift-image-registry.svc:5000")' "${manifest}" - invoke_cluster_cli apply -f "${manifest}" + if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then + debugf "Converting CatalogSource to ClusterCatalog: ${manifest}" + catalogImage=$("$YQ" '.spec.image' "${manifest}") + catalogImage=$(echo "$catalogImage" | sed 's|default-route-openshift-image-registry\.apps\.[^/]*|image-registry.openshift-image-registry.svc:5000|') + cat </dev/null; then - errorf " + if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then + if ! invoke_cluster_cli get crd clusterextensions.olm.operatorframework.io &>/dev/null; then + errorf " + OLM v1 not installed (ClusterExtension CRD not found) or you don't have enough permissions. + Check that you are correctly logged into the cluster and that OLM v1 is installed." + exit 1 + fi + else + if ! invoke_cluster_cli get crd catalogsources.operators.coreos.com &>/dev/null; then + errorf " OLM not installed (CatalogSource CRD not found) or you don't have enough permissions. Check that you are correctly logged into the cluster and that OLM is installed. See https://olm.operatorframework.io/docs/getting-started/#installing-olm-in-your-cluster to install OLM." - exit 1 + exit 1 + fi fi fi @@ -1106,7 +1235,23 @@ else my_operator_index="$(buildCatalogImageUrl "internal")" fi - cat <"${manifestsTargetDir}/catalogSource.yaml" + if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; }; then + cat <"${manifestsTargetDir}/clusterCatalog.yaml" +apiVersion: catalogd.operatorframework.io/v1 +kind: ClusterCatalog +metadata: + name: rhdh-catalog +spec: + source: + type: Image + image: + ref: ${my_operator_index} + pullSecret: internal-reg-auth-for-rhdh +EOF + fi + + if [[ "${RESOLVED_OLM_VERSION}" != "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; }; then + cat <"${manifestsTargetDir}/catalogSource.yaml" apiVersion: operators.coreos.com/v1alpha1 kind: CatalogSource metadata: @@ -1123,6 +1268,7 @@ else publisher: "Red Hat" displayName: "Red Hat Developer Hub (Airgapped)" EOF + fi if [[ -n "${TO_REGISTRY}" ]]; then # IDMS will only work on regular OCP clusters. It doesn't work on ROSA or clusters with hosted control planes like on IBM Cloud. @@ -1205,7 +1351,11 @@ EOF invoke_cluster_cli apply -f "${manifestsTargetDir}/imageDigestMirrorSet.yaml" fi debugf "Adding the internal cluster creds as pull secrets to be able to pull images from this internal registry by default" - invoke_cluster_cli apply -f "${manifestsTargetDir}/catalogSource.yaml" + if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then + invoke_cluster_cli apply -f "${manifestsTargetDir}/clusterCatalog.yaml" + else + invoke_cluster_cli apply -f "${manifestsTargetDir}/catalogSource.yaml" + fi fi fi @@ -1224,7 +1374,73 @@ metadata: name: ${NAMESPACE_OPERATOR} EOF -cat <"${manifestsTargetDir}/operatorGroup.yaml" +if [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; then + # Export-only mode with auto-detection: generate both v0 and v1 manifest templates + # so the user has them available when importing on the target cluster. + # During import (--from-dir + --to-registry), manifests are regenerated with real values. + infof "Generating both OLM v0 and v1 manifest templates for export." +fi + +if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; }; then + # shellcheck disable=SC2016 + SA_NAME='${SA_NAME}' + # shellcheck disable=SC2016 + CRB_NAME='${CRB_NAME}' + if [[ -n "${TO_REGISTRY}" ]]; then + SA_NAME="rhdh-operator-installer" + CRB_NAME="rhdh-operator-installer-binding" + fi + + cat <"${manifestsTargetDir}/serviceAccount.yaml" +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ${SA_NAME} + namespace: ${NAMESPACE_OPERATOR} +EOF + + cat <"${manifestsTargetDir}/clusterRoleBinding.yaml" +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: ${CRB_NAME} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: ${SA_NAME} + namespace: ${NAMESPACE_OPERATOR} +EOF + + cat <"${manifestsTargetDir}/clusterExtension.yaml" +apiVersion: olm.operatorframework.io/v1 +kind: ClusterExtension +metadata: + name: rhdh-operator +spec: + namespace: ${NAMESPACE_OPERATOR} + serviceAccount: + name: ${SA_NAME} + source: + sourceType: Catalog + catalog: + packageName: rhdh + channels: + - name: fast + selector: + matchLabels: + olm.operatorframework.io/metadata.name: rhdh-catalog + install: + preflight: + crdUpgradeSafety: + enforcement: None +EOF +fi + +if [[ "${RESOLVED_OLM_VERSION}" != "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; }; then + cat <"${manifestsTargetDir}/operatorGroup.yaml" apiVersion: operators.coreos.com/v1 kind: OperatorGroup metadata: @@ -1232,7 +1448,7 @@ metadata: namespace: ${NAMESPACE_OPERATOR} EOF -cat <"${manifestsTargetDir}/subscription.yaml" + cat <"${manifestsTargetDir}/subscription.yaml" apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: @@ -1245,6 +1461,7 @@ spec: source: rhdh-catalog sourceNamespace: ${NAMESPACE_CATALOGSOURCE} EOF +fi if [[ "$INSTALL_OPERATOR" != "true" ]]; then echo @@ -1264,7 +1481,15 @@ ${TO_DIR} should now contain all the images and resources needed to install the " fi if [[ -n "${TO_REGISTRY}" ]]; then - if [[ "${IS_OPENSHIFT}" = "true" ]]; then + if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then + echo "To install the operator via OLM v1, apply the following manifests: + + kubectl apply -f ${manifestsTargetDir}/namespace.yaml + kubectl apply -f ${manifestsTargetDir}/serviceAccount.yaml + kubectl apply -f ${manifestsTargetDir}/clusterRoleBinding.yaml + kubectl apply -f ${manifestsTargetDir}/clusterExtension.yaml + " + elif [[ "${IS_OPENSHIFT}" = "true" ]]; then echo "Now log into the OCP web console as an admin, then go to Operators > OperatorHub, search for Red Hat Developer Hub, and install the Red Hat Developer Hub Operator." else echo "To install the operator, you will need to create an OperatorGroup and a Subscription. You can do so with the following commands: @@ -1281,33 +1506,84 @@ fi if [[ -n "${TO_REGISTRY}" ]]; then # Install the operator - for manifest in namespace operatorGroup subscription; do - invoke_cluster_cli apply -f "${manifestsTargetDir}/${manifest}.yaml" - done + if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then + for manifest in namespace serviceAccount clusterRoleBinding clusterExtension; do + invoke_cluster_cli apply -f "${manifestsTargetDir}/${manifest}.yaml" + done + else + for manifest in namespace operatorGroup subscription; do + invoke_cluster_cli apply -f "${manifestsTargetDir}/${manifest}.yaml" + done + fi invoke_cluster_cli -n ${NAMESPACE_OPERATOR} patch serviceaccount default \ -p '{"imagePullSecrets": [{"name": "internal-reg-auth-for-rhdh"},{"name": "internal-reg-ext-auth-for-rhdh"},{"name": "reg-pull-secret"}]}' + CLI_TOOL="kubectl" if [[ "${IS_OPENSHIFT}" = "true" ]]; then - OCP_CONSOLE_ROUTE_HOST=$(invoke_cluster_cli get route console -n openshift-console -o=jsonpath='{.spec.host}') - CLUSTER_ROUTER_BASE=$(invoke_cluster_cli get ingress.config.openshift.io/cluster '-o=jsonpath={.spec.domain}') - echo -n " + CLI_TOOL="oc" + fi + + if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then + OCP_CONSOLE_ROUTE_HOST=$(invoke_cluster_cli get route console -n openshift-console -o=jsonpath='{.spec.host}' 2>/dev/null || true) + CLUSTER_ROUTER_BASE=$(invoke_cluster_cli get ingress.config.openshift.io/cluster '-o=jsonpath={.spec.domain}' 2>/dev/null || true) + + CR_EXAMPLE=" + cat < Date: Thu, 25 Jun 2026 03:46:54 +0100 Subject: [PATCH 02/30] Enhance OLM manifest generation logic in prepare-restricted-environment.sh - Introduced functions to determine whether to generate v1 or v0 manifests based on the resolved OLM version and registry settings. - Simplified conditional checks for OLM v1 and v0 manifest generation, improving code readability and maintainability. - Updated error handling for OLM installation checks to reflect the new logic. --- .../scripts/prepare-restricted-environment.sh | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 581dc7586..03926b64f 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -418,6 +418,14 @@ function prepare_olm_v1_secrets() { done } +function should_generate_v1_manifests() { + [[ "${RESOLVED_OLM_VERSION}" == "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; } +} + +function should_generate_v0_manifests() { + [[ "${RESOLVED_OLM_VERSION}" != "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; } +} + ########################################################################################## # Script start ########################################################################################## @@ -1208,22 +1216,13 @@ else exit 1 fi debugf "Falling back to a standard K8s cluster" - # Check that OLM is installed - if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then - if ! invoke_cluster_cli get crd clusterextensions.olm.operatorframework.io &>/dev/null; then - errorf " - OLM v1 not installed (ClusterExtension CRD not found) or you don't have enough permissions. - Check that you are correctly logged into the cluster and that OLM v1 is installed." - exit 1 - fi - else - if ! invoke_cluster_cli get crd catalogsources.operators.coreos.com &>/dev/null; then - errorf " + # OLM v1 on Kubernetes is not supported (resolve_olm_version forces v0 for non-OpenShift) + if ! invoke_cluster_cli get crd catalogsources.operators.coreos.com &>/dev/null; then + errorf " OLM not installed (CatalogSource CRD not found) or you don't have enough permissions. Check that you are correctly logged into the cluster and that OLM is installed. See https://olm.operatorframework.io/docs/getting-started/#installing-olm-in-your-cluster to install OLM." - exit 1 - fi + exit 1 fi fi @@ -1235,7 +1234,7 @@ else my_operator_index="$(buildCatalogImageUrl "internal")" fi - if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; }; then + if should_generate_v1_manifests; then cat <"${manifestsTargetDir}/clusterCatalog.yaml" apiVersion: catalogd.operatorframework.io/v1 kind: ClusterCatalog @@ -1250,7 +1249,7 @@ spec: EOF fi - if [[ "${RESOLVED_OLM_VERSION}" != "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; }; then + if should_generate_v0_manifests; then cat <"${manifestsTargetDir}/catalogSource.yaml" apiVersion: operators.coreos.com/v1alpha1 kind: CatalogSource @@ -1381,7 +1380,7 @@ if [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; then infof "Generating both OLM v0 and v1 manifest templates for export." fi -if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; }; then +if should_generate_v1_manifests; then # shellcheck disable=SC2016 SA_NAME='${SA_NAME}' # shellcheck disable=SC2016 @@ -1399,6 +1398,8 @@ metadata: namespace: ${NAMESPACE_OPERATOR} EOF + # cluster-admin is used as a convenience for airgap environments. + # For production, scope down per: https://operator-framework.github.io/operator-controller/howto/derive-service-account/ cat <"${manifestsTargetDir}/clusterRoleBinding.yaml" apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -1439,7 +1440,7 @@ spec: EOF fi -if [[ "${RESOLVED_OLM_VERSION}" != "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; }; then +if should_generate_v0_manifests; then cat <"${manifestsTargetDir}/operatorGroup.yaml" apiVersion: operators.coreos.com/v1 kind: OperatorGroup From 58f282c0dcb3bd21d313908891cccb27ee81d89f Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Thu, 25 Jun 2026 11:12:55 +0100 Subject: [PATCH 03/30] Update pull secret handling in prepare-restricted-environment.sh - Introduced a variable CATALOG_PULL_SECRET to dynamically set the pull secret based on the TO_REGISTRY value. - Updated manifest generation logic to use the new CATALOG_PULL_SECRET variable, enhancing flexibility for different registry configurations. - Improved code readability by consolidating pull secret assignments. --- .rhdh/scripts/prepare-restricted-environment.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 03926b64f..559045fee 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -19,6 +19,7 @@ INDEX_IMAGE="registry.redhat.io/redhat/redhat-operator-index:v4.18" FILTERED_VERSIONS=(*) OLM_VERSION="auto" RESOLVED_OLM_VERSION="" +CATALOG_PULL_SECRET="" # assume mikefarah version of yq is already available on the path; if 1, then install the version shown INSTALL_YQ=0 @@ -976,6 +977,11 @@ fi if [[ -n "${TO_REGISTRY}" ]]; then resolve_olm_version + if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then + CATALOG_PULL_SECRET="internal-reg-auth-for-rhdh" + else + CATALOG_PULL_SECRET="reg-pull-secret" + fi if [[ "${IS_OPENSHIFT}" = "true" && "${TO_REGISTRY}" = "OCP_INTERNAL" && "${RESOLVED_OLM_VERSION}" == "v1" ]]; then prepare_olm_v1_secrets fi @@ -1155,7 +1161,7 @@ spec: type: Image image: ref: ${catalogImage} - pullSecret: internal-reg-auth-for-rhdh + pullSecret: ${CATALOG_PULL_SECRET} EOF else debugf "Processing CatalogSource: ${manifest}" @@ -1196,6 +1202,8 @@ else NAMESPACE_CATALOGSOURCE='$NAMESPACE_CATALOGSOURCE' # shellcheck disable=SC2016 my_operator_index='$CATALOG_IMAGE' + # shellcheck disable=SC2016 + CATALOG_PULL_SECRET='$CATALOG_PULL_SECRET' if [[ -n "${TO_REGISTRY}" ]]; then # It assumes that the user is also connected to a cluster detect_ocp_and_set_env_var @@ -1232,6 +1240,11 @@ else NAMESPACE_CATALOGSOURCE="olm" fi my_operator_index="$(buildCatalogImageUrl "internal")" + if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then + CATALOG_PULL_SECRET="internal-reg-auth-for-rhdh" + else + CATALOG_PULL_SECRET="reg-pull-secret" + fi fi if should_generate_v1_manifests; then @@ -1245,7 +1258,7 @@ spec: type: Image image: ref: ${my_operator_index} - pullSecret: internal-reg-auth-for-rhdh + pullSecret: ${CATALOG_PULL_SECRET} EOF fi From 5cb6ebd89b40d7ae441b0c9dcae730e965d56219 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Thu, 25 Jun 2026 11:18:31 +0100 Subject: [PATCH 04/30] Refactor buildRegistryUrl function in prepare-restricted-environment.sh - Updated the buildRegistryUrl function to directly execute the oc command for retrieving the default route, improving clarity. - Added a shellcheck directive to suppress warnings for the catalogImage variable assignment, enhancing code quality and maintainability. --- .rhdh/scripts/prepare-restricted-environment.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 559045fee..9e9d0f901 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -551,7 +551,7 @@ function buildRegistryUrl() { if [[ "${input}" == "internal" ]]; then echo "image-registry.openshift-image-registry.svc:5000" else - echo "$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}')" + oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}' fi else echo "${TO_REGISTRY}" @@ -1150,6 +1150,7 @@ EOF if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then debugf "Converting CatalogSource to ClusterCatalog: ${manifest}" catalogImage=$("$YQ" '.spec.image' "${manifest}") + # shellcheck disable=SC2001 catalogImage=$(echo "$catalogImage" | sed 's|default-route-openshift-image-registry\.apps\.[^/]*|image-registry.openshift-image-registry.svc:5000|') cat < Date: Thu, 25 Jun 2026 12:09:57 +0100 Subject: [PATCH 05/30] Add ClusterRole and update manifest generation in prepare-restricted-environment.sh - Introduced a new ClusterRole for the rhdh-operator-installer, defining permissions for various Kubernetes resources. - Updated the manifest generation logic to include the new ClusterRole and adjusted the ClusterRoleBinding to reference it. - Enhanced the script to apply the ClusterRole during the installation process, improving operator functionality and security. --- .../scripts/prepare-restricted-environment.sh | 94 ++++++++++++++++++- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 9e9d0f901..413281d68 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1399,9 +1399,12 @@ if should_generate_v1_manifests; then SA_NAME='${SA_NAME}' # shellcheck disable=SC2016 CRB_NAME='${CRB_NAME}' + # shellcheck disable=SC2016 + CR_NAME='${CR_NAME}' if [[ -n "${TO_REGISTRY}" ]]; then SA_NAME="rhdh-operator-installer" CRB_NAME="rhdh-operator-installer-binding" + CR_NAME="rhdh-operator-installer-role" fi cat <"${manifestsTargetDir}/serviceAccount.yaml" @@ -1412,8 +1415,90 @@ metadata: namespace: ${NAMESPACE_OPERATOR} EOF - # cluster-admin is used as a convenience for airgap environments. - # For production, scope down per: https://operator-framework.github.io/operator-controller/howto/derive-service-account/ + cat <"${manifestsTargetDir}/clusterRole.yaml" +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ${CR_NAME} +rules: +# Bundle resource management: CRDs, RBAC, core resources +- apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +- apiGroups: [""] + resources: ["serviceaccounts"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +# Operator runtime: core resources +- apiGroups: [""] + resources: ["configmaps", "persistentvolumeclaims", "secrets", "services"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +- apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +# Operator runtime: workloads +- apiGroups: ["apps"] + resources: ["deployments", "statefulsets"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +- apiGroups: ["batch"] + resources: ["jobs"] + verbs: ["create", "delete", "patch", "update"] +# Operator runtime: OpenShift +- apiGroups: ["config.openshift.io"] + resources: ["ingresses"] + verbs: ["get"] +- apiGroups: ["route.openshift.io"] + resources: ["routes", "routes/custom-host"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +# Operator runtime: monitoring +- apiGroups: ["monitoring.coreos.com"] + resources: ["servicemonitors"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +# Operator runtime: RHDH CRDs +- apiGroups: ["rhdh.redhat.com"] + resources: ["backstages"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +- apiGroups: ["rhdh.redhat.com"] + resources: ["backstages/finalizers"] + verbs: ["update"] +- apiGroups: ["rhdh.redhat.com"] + resources: ["backstages/status"] + verbs: ["get", "patch", "update"] +# Operator runtime: plugin integrations +- apiGroups: ["sonataflow.org"] + resources: ["sonataflowplatforms", "sonataflows"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +- apiGroups: ["networking.k8s.io"] + resources: ["networkpolicies"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +- apiGroups: ["tekton.dev"] + resources: ["tasks", "pipelines"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +- apiGroups: ["argoproj.io"] + resources: ["appprojects"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +# Operator runtime: auth +- apiGroups: ["authentication.k8s.io"] + resources: ["tokenreviews"] + verbs: ["create"] +- apiGroups: ["authorization.k8s.io"] + resources: ["subjectaccessreviews"] + verbs: ["create"] +# Operator runtime: leader election +- apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["create", "delete", "get", "list", "patch", "update", "watch"] +# OLM v1 lifecycle +- apiGroups: ["olm.operatorframework.io"] + resources: ["clusterextensions/finalizers"] + verbs: ["update"] +EOF + cat <"${manifestsTargetDir}/clusterRoleBinding.yaml" apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -1422,7 +1507,7 @@ metadata: roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: cluster-admin + name: ${CR_NAME} subjects: - kind: ServiceAccount name: ${SA_NAME} @@ -1501,6 +1586,7 @@ ${TO_DIR} should now contain all the images and resources needed to install the kubectl apply -f ${manifestsTargetDir}/namespace.yaml kubectl apply -f ${manifestsTargetDir}/serviceAccount.yaml + kubectl apply -f ${manifestsTargetDir}/clusterRole.yaml kubectl apply -f ${manifestsTargetDir}/clusterRoleBinding.yaml kubectl apply -f ${manifestsTargetDir}/clusterExtension.yaml " @@ -1522,7 +1608,7 @@ if [[ -n "${TO_REGISTRY}" ]]; then # Install the operator if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then - for manifest in namespace serviceAccount clusterRoleBinding clusterExtension; do + for manifest in namespace serviceAccount clusterRole clusterRoleBinding clusterExtension; do invoke_cluster_cli apply -f "${manifestsTargetDir}/${manifest}.yaml" done else From ae5e5cf6be2655a9b69cd45d9f840967e91af28b Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Thu, 25 Jun 2026 12:11:52 +0100 Subject: [PATCH 06/30] Enhance OLM detection logic in prepare-restricted-environment.sh - Renamed the detect_olm_v1 function to detect_olm_v1_crd for clarity. - Introduced a new function, detect_olm_v1_catalogd, to check for the presence of the catalogd deployment. - Updated the resolve_olm_version function to include checks for both the ClusterExtension CRD and catalogd, improving error handling and detection accuracy for OLM v1. --- .../scripts/prepare-restricted-environment.sh | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 413281d68..bc836ba73 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -343,10 +343,20 @@ function invoke_cluster_cli() { fi } -function detect_olm_v1() { +function detect_olm_v1_crd() { invoke_cluster_cli get crd clusterextensions.olm.operatorframework.io &>/dev/null } +function detect_olm_v1_catalogd() { + local ns + ns=$(invoke_cluster_cli get deployment -A -l 'app.kubernetes.io/name=catalogd' \ + -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true) + if [[ -z "${ns}" ]]; then + ns="openshift-catalogd" + fi + invoke_cluster_cli get namespace "${ns}" &>/dev/null +} + function resolve_olm_version() { set -euo pipefail @@ -362,17 +372,26 @@ function resolve_olm_version() { RESOLVED_OLM_VERSION="v0" infof "Using OLM v0 (forced via --olm-version)" elif [[ "${OLM_VERSION}" == "v1" ]]; then - if ! detect_olm_v1; then + if ! detect_olm_v1_crd; then errorf "OLM v1 requested but ClusterExtension CRD not found on this cluster" exit 1 fi + if ! detect_olm_v1_catalogd; then + errorf "OLM v1 requested but catalogd is not installed or its namespace is missing" + exit 1 + fi RESOLVED_OLM_VERSION="v1" infof "Using OLM v1 (forced via --olm-version)" else # auto-detect - if detect_olm_v1; then - RESOLVED_OLM_VERSION="v1" - infof "Auto-detected OLM v1 (ClusterExtension CRD found)" + if detect_olm_v1_crd; then + if detect_olm_v1_catalogd; then + RESOLVED_OLM_VERSION="v1" + infof "Auto-detected OLM v1 (ClusterExtension CRD and catalogd found)" + else + RESOLVED_OLM_VERSION="v0" + warnf "ClusterExtension CRD found but catalogd is not ready; falling back to OLM v0" + fi else RESOLVED_OLM_VERSION="v0" infof "Auto-detected OLM v0 (ClusterExtension CRD not found)" From 50bb1df85c43659152d3e6e34c4392107cfa9284 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Thu, 25 Jun 2026 12:15:54 +0100 Subject: [PATCH 07/30] Validate OLM version input in prepare-restricted-environment.sh - Added a check to ensure the '--olm-version' argument is provided with a valid value (v0, v1, or auto). - Improved error handling by displaying a usage message when the input is invalid, enhancing user experience and script robustness. --- .rhdh/scripts/prepare-restricted-environment.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index bc836ba73..123c2a38d 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -116,7 +116,8 @@ Options: --oc-mirror-path : Path to the oc-mirror binary (default: 'oc-mirror'). --oc-mirror-flags : Additional flags to pass to all oc-mirror commands. --olm-version v0|v1|auto : Force OLM version for catalog/operator resources (default: auto-detect). - 'auto' detects OLM v1 by checking for the ClusterExtension CRD on the cluster. + 'auto' detects OLM v1 by checking for the ClusterExtension CRD and + a running catalogd deployment on the cluster. When v1 is detected or forced, generates ClusterCatalog + ClusterExtension instead of CatalogSource + Subscription. --install-yq : Install yq $YQ_VERSION from https://github.com/mikefarah/yq (not the jq python wrapper) @@ -273,6 +274,11 @@ while [[ "$#" -gt 0 ]]; do shift 1 ;; '--olm-version') + if [[ $# -lt 2 ]]; then + errorf "--olm-version requires a value (v0, v1, or auto)." + usage + exit 1 + fi if [[ "$2" != "v0" && "$2" != "v1" && "$2" != "auto" ]]; then errorf "Unknown OLM version: $2. Must be v0, v1, or auto." usage From 56ac6ba5bfdbd5c327d8069e91b21bf2406f167a Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Thu, 25 Jun 2026 13:01:22 +0100 Subject: [PATCH 08/30] Update FILTERED_VERSIONS variable in prepare-restricted-environment.sh - Changed the assignment of FILTERED_VERSIONS to use a quoted wildcard, improving compatibility with shell globbing. - This adjustment enhances the script's flexibility in handling version filtering. --- .rhdh/scripts/prepare-restricted-environment.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 123c2a38d..847d7a742 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -16,7 +16,7 @@ IS_HOSTED_CONTROL_PLANE="" NAMESPACE_OPERATOR="rhdh-operator" INDEX_IMAGE="registry.redhat.io/redhat/redhat-operator-index:v4.18" -FILTERED_VERSIONS=(*) +FILTERED_VERSIONS=("*") OLM_VERSION="auto" RESOLVED_OLM_VERSION="" CATALOG_PULL_SECRET="" From 7e0432aa61f8e6928f28ce5743459d26872dc65e Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Sun, 28 Jun 2026 21:15:11 +0100 Subject: [PATCH 09/30] Update version filtering and add opm installation check in prepare-restricted-environment.sh - Changed the version filter in the script to '1.9,1.10' for improved compatibility with the latest releases. - Added a check for the opm tool installation when USE_OC_MIRROR is not set to true, enhancing error handling and user guidance. --- .rhdh/scripts/prepare-restricted-environment.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 847d7a742..0d4ba4a32 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -147,7 +147,7 @@ Examples: # It will automatically replace all references to the internal RH registries with quay.io $0 \\ --ci-index true \\ - --filter-versions '1.4,1.5' + --filter-versions '1.9,1.10' # WORKFLOW with oc-mirror v2 for fully disconnected environments: # (on connected host): Export images to disk @@ -987,6 +987,12 @@ function push_image_from_archive() { check_tool "yq" check_tool "umoci" check_tool "skopeo" +if [[ "${USE_OC_MIRROR}" != "true" ]]; then + if ! command -v opm &>/dev/null; then + errorf "Please install opm v1.47+. See https://github.com/operator-framework/operator-registry/releases" + exit 1 + fi +fi if [[ -n "$TO_REGISTRY" ]]; then check_tool "podman" fi From a744c7c40ccd0ea2527cd8628798733290404622 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Sun, 28 Jun 2026 23:46:52 +0100 Subject: [PATCH 10/30] Refactor related images handling and update OLM API version in prepare-restricted-environment.sh - Improved the processing of related images by ensuring only non-empty values are added to the all_related_images array. - Updated the API version in the ClusterCatalog manifest from 'catalogd.operatorframework.io/v1' to 'olm.operatorframework.io/v1' for better alignment with OLM standards. --- .rhdh/scripts/prepare-restricted-environment.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 0d4ba4a32..2c0c48e39 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -898,7 +898,9 @@ function process_bundles_from_dir() { # TODO(rm3l): we should use spec.relatedImages instead, but it seems to be incomplete in some bundles related_images=$("$YQ" '.spec.install.spec.deployments[].spec.template.spec.containers[].env[] | select(.name | test("^RELATED_IMAGE_")).value' "$file" || true) if [[ -n "$related_images" ]]; then - all_related_images+=("$related_images") + while IFS= read -r img; do + [[ -n "$img" ]] && all_related_images+=("$img") + done <<<"$related_images" fi for relatedImage in "${all_related_images[@]}"; do imgDir="${FROM_DIR}/images/" @@ -1184,7 +1186,7 @@ EOF # shellcheck disable=SC2001 catalogImage=$(echo "$catalogImage" | sed 's|default-route-openshift-image-registry\.apps\.[^/]*|image-registry.openshift-image-registry.svc:5000|') cat <"${manifestsTargetDir}/clusterCatalog.yaml" -apiVersion: catalogd.operatorframework.io/v1 +apiVersion: olm.operatorframework.io/v1 kind: ClusterCatalog metadata: name: rhdh-catalog From 4a483a556aa5b4ee9b421c78d92ab2f2b1da5831 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Tue, 30 Jun 2026 14:13:59 +0100 Subject: [PATCH 11/30] Update OLM deployment checks and manifest application in prepare-restricted-environment.sh - Modified the detect_olm_v1_catalogd function to return an error if the catalogd namespace is not found, improving error handling. - Updated the manifest application logic to include clusterCatalog.yaml, ensuring all necessary resources are applied during OLM v1 installation. --- .rhdh/scripts/prepare-restricted-environment.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 2c0c48e39..eb366f8ee 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -358,9 +358,9 @@ function detect_olm_v1_catalogd() { ns=$(invoke_cluster_cli get deployment -A -l 'app.kubernetes.io/name=catalogd' \ -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true) if [[ -z "${ns}" ]]; then - ns="openshift-catalogd" + return 1 fi - invoke_cluster_cli get namespace "${ns}" &>/dev/null + invoke_cluster_cli rollout status deployment -n "${ns}" -l 'app.kubernetes.io/name=catalogd' --timeout=5s &>/dev/null } function resolve_olm_version() { @@ -1618,6 +1618,7 @@ ${TO_DIR} should now contain all the images and resources needed to install the echo "To install the operator via OLM v1, apply the following manifests: kubectl apply -f ${manifestsTargetDir}/namespace.yaml + kubectl apply -f ${manifestsTargetDir}/clusterCatalog.yaml kubectl apply -f ${manifestsTargetDir}/serviceAccount.yaml kubectl apply -f ${manifestsTargetDir}/clusterRole.yaml kubectl apply -f ${manifestsTargetDir}/clusterRoleBinding.yaml @@ -1641,7 +1642,7 @@ if [[ -n "${TO_REGISTRY}" ]]; then # Install the operator if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then - for manifest in namespace serviceAccount clusterRole clusterRoleBinding clusterExtension; do + for manifest in namespace clusterCatalog serviceAccount clusterRole clusterRoleBinding clusterExtension; do invoke_cluster_cli apply -f "${manifestsTargetDir}/${manifest}.yaml" done else From 8e966e0dbee68e029a565f7c98865a93594b5074 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Thu, 2 Jul 2026 18:22:58 +0100 Subject: [PATCH 12/30] fix: use plain strings for channels in ClusterExtension manifest The OLM v1 ClusterExtension API expects spec.source.catalog.channels to be an array of strings, not an array of objects. Changed `- name: fast` to `- fast`. Ref: RHIDP-14785 Signed-off-by: Fortune-Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index eb366f8ee..2bbbc8fae 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1561,7 +1561,7 @@ spec: catalog: packageName: rhdh channels: - - name: fast + - fast selector: matchLabels: olm.operatorframework.io/metadata.name: rhdh-catalog From a76338a0bc888d8daabe5953a13e02a3df75e8c3 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Mon, 6 Jul 2026 10:25:08 +0100 Subject: [PATCH 13/30] Refactor OLM version validation to use case statement Replace chained conditional checks with a case pattern match for improved readability and maintainability. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 2bbbc8fae..692f5f04a 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -279,11 +279,14 @@ while [[ "$#" -gt 0 ]]; do usage exit 1 fi - if [[ "$2" != "v0" && "$2" != "v1" && "$2" != "auto" ]]; then - errorf "Unknown OLM version: $2. Must be v0, v1, or auto." - usage - exit 1 - fi + case "$2" in + v0|v1|auto) ;; + *) + errorf "Unknown OLM version: $2. Must be v0, v1, or auto." + usage + exit 1 + ;; + esac OLM_VERSION="$2" shift 1 ;; From eec8f8cc3077d3465ad375cfa4f25fe66dbf0b11 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Mon, 6 Jul 2026 10:26:10 +0100 Subject: [PATCH 14/30] Remove OpenShift-only restriction from OLM v1 detection OLM v1 auto-detection is now platform-agnostic, based solely on CRD presence and catalogd deployment status regardless of cluster type. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 692f5f04a..894e05997 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -369,14 +369,6 @@ function detect_olm_v1_catalogd() { function resolve_olm_version() { set -euo pipefail - if [[ "${IS_OPENSHIFT}" != "true" ]]; then - if [[ "${OLM_VERSION}" == "v1" ]]; then - warnf "OLM v1 is not supported on Kubernetes clusters; falling back to v0" - fi - RESOLVED_OLM_VERSION="v0" - return - fi - if [[ "${OLM_VERSION}" == "v0" ]]; then RESOLVED_OLM_VERSION="v0" infof "Using OLM v0 (forced via --olm-version)" From ef19992575fd0e533686dc1ff0f37981c8aee4bf Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Mon, 6 Jul 2026 10:26:38 +0100 Subject: [PATCH 15/30] Add comments to clarify should_generate_v1/v0_manifests logic Explain when each function returns true: either the resolved OLM version matches, or we are exporting to disk with auto-detect mode and need to generate both sets of manifests. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 894e05997..e10df846c 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -439,10 +439,18 @@ function prepare_olm_v1_secrets() { done } +# Generate v1 manifests when: +# - OLM v1 was resolved (detected or forced), OR +# - we're exporting to disk (no registry) with auto-detect, so we generate both v0 and v1 +# for the user to choose from in their disconnected environment function should_generate_v1_manifests() { [[ "${RESOLVED_OLM_VERSION}" == "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; } } +# Generate v0 manifests when: +# - OLM v0 was resolved (detected or forced), OR +# - we're exporting to disk (no registry) with auto-detect, so we generate both v0 and v1 +# for the user to choose from in their disconnected environment function should_generate_v0_manifests() { [[ "${RESOLVED_OLM_VERSION}" != "v1" ]] || { [[ -z "${TO_REGISTRY}" ]] && [[ "${OLM_VERSION}" == "auto" ]]; } } From 1bcb723167df0d3e89f1d3ff5af3e1da90ddc80a Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Mon, 6 Jul 2026 10:27:10 +0100 Subject: [PATCH 16/30] Use oc or kubectl in v1 manual install instructions based on cluster type Replace hardcoded kubectl references in the OLM v1 manual install instructions with the appropriate CLI tool for the detected cluster. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index e10df846c..778f2e957 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1617,15 +1617,19 @@ ${TO_DIR} should now contain all the images and resources needed to install the " fi if [[ -n "${TO_REGISTRY}" ]]; then + local cli_hint="kubectl" + if [[ "${IS_OPENSHIFT}" = "true" ]]; then + cli_hint="oc" + fi if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then echo "To install the operator via OLM v1, apply the following manifests: - kubectl apply -f ${manifestsTargetDir}/namespace.yaml - kubectl apply -f ${manifestsTargetDir}/clusterCatalog.yaml - kubectl apply -f ${manifestsTargetDir}/serviceAccount.yaml - kubectl apply -f ${manifestsTargetDir}/clusterRole.yaml - kubectl apply -f ${manifestsTargetDir}/clusterRoleBinding.yaml - kubectl apply -f ${manifestsTargetDir}/clusterExtension.yaml + ${cli_hint} apply -f ${manifestsTargetDir}/namespace.yaml + ${cli_hint} apply -f ${manifestsTargetDir}/clusterCatalog.yaml + ${cli_hint} apply -f ${manifestsTargetDir}/serviceAccount.yaml + ${cli_hint} apply -f ${manifestsTargetDir}/clusterRole.yaml + ${cli_hint} apply -f ${manifestsTargetDir}/clusterRoleBinding.yaml + ${cli_hint} apply -f ${manifestsTargetDir}/clusterExtension.yaml " elif [[ "${IS_OPENSHIFT}" = "true" ]]; then echo "Now log into the OCP web console as an admin, then go to Operators > OperatorHub, search for Red Hat Developer Hub, and install the Red Hat Developer Hub Operator." From 5c38e4de29cd042cb40f54958a8fb17c22a38a35 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Mon, 6 Jul 2026 10:33:22 +0100 Subject: [PATCH 17/30] Use oc-mirror native ClusterCatalog manifests (cc-*.yaml) for OLM v1 oc-mirror v2 already generates ClusterCatalog resources natively with the cc-*.yaml naming pattern. Use those directly instead of manually converting CatalogSource manifests, applying our name override, image URL rewrite, and pullSecret on top. Fall back to the CatalogSource conversion only if no cc-*.yaml files are found. Signed-off-by: Fortune Ndlovu --- .../scripts/prepare-restricted-environment.sh | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 778f2e957..a15c4534d 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1179,16 +1179,31 @@ EOF fi done - # Process CatalogSource resources - # oc-mirror v2 generates files with pattern: cs-*.yaml - for manifest in "${clusterResourcesDir}"/cs-*.yaml "${clusterResourcesDir}"/catalogSource*.yaml; do - if [[ -f "${manifest}" ]]; then - if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then - debugf "Converting CatalogSource to ClusterCatalog: ${manifest}" - catalogImage=$("$YQ" '.spec.image' "${manifest}") - # shellcheck disable=SC2001 - catalogImage=$(echo "$catalogImage" | sed 's|default-route-openshift-image-registry\.apps\.[^/]*|image-registry.openshift-image-registry.svc:5000|') - cat < Date: Mon, 6 Jul 2026 10:38:13 +0100 Subject: [PATCH 18/30] Fix ShellCheck SC2168: remove local keyword outside functions 'local' is only valid inside functions. These variables are in the top-level script body, so drop the local qualifier. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index a15c4534d..a9a170845 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1181,7 +1181,7 @@ EOF if [[ "${RESOLVED_OLM_VERSION}" == "v1" ]]; then # oc-mirror v2 generates native ClusterCatalog manifests with pattern: cc-*.yaml - local foundClusterCatalog=false + foundClusterCatalog=false for manifest in "${clusterResourcesDir}"/cc-*.yaml; do if [[ -f "${manifest}" ]]; then debugf "Processing native ClusterCatalog: ${manifest}" @@ -1640,7 +1640,7 @@ ${TO_DIR} should now contain all the images and resources needed to install the " fi if [[ -n "${TO_REGISTRY}" ]]; then - local cli_hint="kubectl" + cli_hint="kubectl" if [[ "${IS_OPENSHIFT}" = "true" ]]; then cli_hint="oc" fi From 3ef90652939f1910d3732fddbaeb157a21fd6f7a Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Mon, 6 Jul 2026 15:40:12 +0100 Subject: [PATCH 19/30] Add role binding for image-puller to catalogd service accounts This update grants the image-puller role to both the default and catalogd service accounts in the catalogd namespace, allowing them to pull images from the internal registry. This change enhances the permissions necessary for OLM v1 deployments. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index a9a170845..ed45ee8e3 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -437,6 +437,16 @@ function prepare_olm_v1_secrets() { --docker-password="$(oc whoami -t)" \ --docker-email="admin@internal-registry.example.com" >&2 done + + # Grant image-puller to catalogd service accounts so they can pull from the internal registry + local catalogd_sa + catalogd_sa=$(invoke_cluster_cli get deployment -n "${NAMESPACE_CATALOGD}" -l 'app.kubernetes.io/name=catalogd' \ + -o jsonpath='{.items[0].spec.template.spec.serviceAccountName}' 2>/dev/null || true) + if [[ -z "${catalogd_sa}" ]]; then + catalogd_sa="catalogd-controller-manager" + fi + invoke_cluster_cli policy add-role-to-user system:image-puller "system:serviceaccount:${NAMESPACE_CATALOGD}:default" -n "${NAMESPACE_CATALOGD}" >&2 || true + invoke_cluster_cli policy add-role-to-user system:image-puller "system:serviceaccount:${NAMESPACE_CATALOGD}:${catalogd_sa}" -n "${NAMESPACE_CATALOGD}" >&2 || true } # Generate v1 manifests when: From 665bdb680bdcc2587a58ca154d54f986dcd53806 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Tue, 7 Jul 2026 23:40:35 +0100 Subject: [PATCH 20/30] Skip OLM v0 CatalogSource CRD check when OLM v1 is resolved on Kubernetes When RESOLVED_OLM_VERSION is v1 on a non-OpenShift cluster, the CatalogSource CRD check is irrelevant and would incorrectly fail. Guard it behind a v1 check so --olm-version v1 works on Kubernetes clusters that have OLM v1 installed. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index ed45ee8e3..c5fc7bf10 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1294,13 +1294,14 @@ else exit 1 fi debugf "Falling back to a standard K8s cluster" - # OLM v1 on Kubernetes is not supported (resolve_olm_version forces v0 for non-OpenShift) - if ! invoke_cluster_cli get crd catalogsources.operators.coreos.com &>/dev/null; then - errorf " + if [[ "${RESOLVED_OLM_VERSION}" != "v1" ]]; then + if ! invoke_cluster_cli get crd catalogsources.operators.coreos.com &>/dev/null; then + errorf " OLM not installed (CatalogSource CRD not found) or you don't have enough permissions. Check that you are correctly logged into the cluster and that OLM is installed. See https://olm.operatorframework.io/docs/getting-started/#installing-olm-in-your-cluster to install OLM." - exit 1 + exit 1 + fi fi fi From 7c0e560a8c6498361bd4d469d64fa6ef9f5d4f1f Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Tue, 7 Jul 2026 23:41:19 +0100 Subject: [PATCH 21/30] Remove redundant set -euo pipefail from functions The script already sets -euo pipefail at the top level (line 8). Functions run in the same shell, so repeating it inside resolve_olm_version and prepare_olm_v1_secrets is a no-op. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index c5fc7bf10..3d327746c 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -367,8 +367,6 @@ function detect_olm_v1_catalogd() { } function resolve_olm_version() { - set -euo pipefail - if [[ "${OLM_VERSION}" == "v0" ]]; then RESOLVED_OLM_VERSION="v0" infof "Using OLM v0 (forced via --olm-version)" @@ -401,8 +399,6 @@ function resolve_olm_version() { } function prepare_olm_v1_secrets() { - set -euo pipefail - if [[ "${RESOLVED_OLM_VERSION}" != "v1" ]]; then return fi From 9f1508fa4ef7f84ac9e515156a8cf5db0daa4e2f Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Tue, 7 Jul 2026 23:42:36 +0100 Subject: [PATCH 22/30] Cache catalogd namespace from detection to avoid duplicate cluster query detect_olm_v1_catalogd and prepare_olm_v1_secrets both queried the cluster for the catalogd deployment namespace. Move the result into the global NAMESPACE_CATALOGD variable during detection so prepare_olm_v1_secrets can reuse it without a redundant API call. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 3d327746c..830168131 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -20,6 +20,7 @@ FILTERED_VERSIONS=("*") OLM_VERSION="auto" RESOLVED_OLM_VERSION="" CATALOG_PULL_SECRET="" +NAMESPACE_CATALOGD="" # assume mikefarah version of yq is already available on the path; if 1, then install the version shown INSTALL_YQ=0 @@ -357,13 +358,12 @@ function detect_olm_v1_crd() { } function detect_olm_v1_catalogd() { - local ns - ns=$(invoke_cluster_cli get deployment -A -l 'app.kubernetes.io/name=catalogd' \ + NAMESPACE_CATALOGD=$(invoke_cluster_cli get deployment -A -l 'app.kubernetes.io/name=catalogd' \ -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true) - if [[ -z "${ns}" ]]; then + if [[ -z "${NAMESPACE_CATALOGD}" ]]; then return 1 fi - invoke_cluster_cli rollout status deployment -n "${ns}" -l 'app.kubernetes.io/name=catalogd' --timeout=5s &>/dev/null + invoke_cluster_cli rollout status deployment -n "${NAMESPACE_CATALOGD}" -l 'app.kubernetes.io/name=catalogd' --timeout=5s &>/dev/null } function resolve_olm_version() { @@ -403,8 +403,6 @@ function prepare_olm_v1_secrets() { return fi - NAMESPACE_CATALOGD=$(invoke_cluster_cli get deployment -A -l 'app.kubernetes.io/name=catalogd' \ - -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true) if [[ -z "${NAMESPACE_CATALOGD}" ]]; then NAMESPACE_CATALOGD="openshift-catalogd" fi From 8ce1dc01534408912bab5b5a2266c5421b20d8a4 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Tue, 7 Jul 2026 23:43:24 +0100 Subject: [PATCH 23/30] Remove duplicate CATALOG_PULL_SECRET assignment in non-oc-mirror path CATALOG_PULL_SECRET is already set in the main flow before the oc-mirror/non-oc-mirror split (when TO_REGISTRY is set). The identical assignment inside the non-oc-mirror path was redundant. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 830168131..8b0d958a2 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1305,11 +1305,6 @@ else NAMESPACE_CATALOGSOURCE="olm" fi my_operator_index="$(buildCatalogImageUrl "internal")" - if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then - CATALOG_PULL_SECRET="internal-reg-auth-for-rhdh" - else - CATALOG_PULL_SECRET="reg-pull-secret" - fi fi if should_generate_v1_manifests; then From 1671f11001dcb038280d99e18a0b45ddc41fa6fe Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Tue, 7 Jul 2026 23:46:13 +0100 Subject: [PATCH 24/30] Add comment explaining why CRD upgrade safety preflight is disabled Reference RHIDP-8656 in the generated ClusterExtension manifest so users inspecting the YAML understand why enforcement is set to None. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 8b0d958a2..5d8db9ada 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1594,6 +1594,7 @@ spec: install: preflight: crdUpgradeSafety: + # Disabled due to known blocker RHIDP-8656 enforcement: None EOF fi From c317ca366ce23336c180fdff0996435e192f2ce0 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Tue, 7 Jul 2026 23:50:52 +0100 Subject: [PATCH 25/30] Fix crdUpgradeSafety comment to reference upstream OLM v1 bug RHIDP-8656 was the spike investigation, not a blocker. The actual reason for disabling CRD upgrade safety preflight is upstream OLM v1 bug OCPBUGS-60693, which is fixed in OCP 4.22. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 5d8db9ada..a7df7b9ec 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1594,7 +1594,7 @@ spec: install: preflight: crdUpgradeSafety: - # Disabled due to known blocker RHIDP-8656 + # Workaround for upstream OLM v1 bug OCPBUGS-60693, fixed in OCP 4.22 enforcement: None EOF fi From 85cd9507054a552ee23bdece046172c9a0b0cd5e Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Tue, 7 Jul 2026 23:54:44 +0100 Subject: [PATCH 26/30] Remove crdUpgradeSafety preflight override from ClusterExtension The upstream OLM v1 bug (OCPBUGS-60693) that required disabling CRD upgrade safety validation is fixed in OCP 4.22. Remove the workaround so the default Strict enforcement applies. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index a7df7b9ec..e8d446597 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1591,11 +1591,6 @@ spec: selector: matchLabels: olm.operatorframework.io/metadata.name: rhdh-catalog - install: - preflight: - crdUpgradeSafety: - # Workaround for upstream OLM v1 bug OCPBUGS-60693, fixed in OCP 4.22 - enforcement: None EOF fi From a2b90a03101a6deaa03a2876acf1855ff5521f02 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Wed, 8 Jul 2026 00:51:09 +0100 Subject: [PATCH 27/30] Restore CATALOG_PULL_SECRET assignment in non-oc-mirror path The template-mode code at line 1270 overwrites CATALOG_PULL_SECRET with a literal '$CATALOG_PULL_SECRET' placeholder for export-to-dir. When TO_REGISTRY is set, this must be re-assigned to the actual secret name so the generated ClusterCatalog manifest has a valid pullSecret reference. Without this, catalogd gets 'authentication required' errors pulling from the internal registry. Signed-off-by: Fortune Ndlovu --- .rhdh/scripts/prepare-restricted-environment.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index e8d446597..35c1a7cda 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -1305,6 +1305,11 @@ else NAMESPACE_CATALOGSOURCE="olm" fi my_operator_index="$(buildCatalogImageUrl "internal")" + if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then + CATALOG_PULL_SECRET="internal-reg-auth-for-rhdh" + else + CATALOG_PULL_SECRET="reg-pull-secret" + fi fi if should_generate_v1_manifests; then From 236295199428f88bfada476ef30834e4259941af Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Wed, 8 Jul 2026 01:21:30 +0100 Subject: [PATCH 28/30] Use global pull secret for OLM v1 catalog and bundle authentication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The olm.operatorframework.io/v1 ClusterCatalog CRD does not have a pullSecret field — it was silently dropped, causing 'authentication required' errors when catalogd tried to pull from the internal registry. OLM v1 catalogd and operator-controller both authenticate via the global pull secret (openshift-config/pull-secret). Merge internal registry credentials there instead of relying on a per-resource pullSecret field that does not exist in the CRD schema. Signed-off-by: Fortune Ndlovu --- .../scripts/prepare-restricted-environment.sh | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 35c1a7cda..03bfeb654 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -413,22 +413,47 @@ function prepare_olm_v1_secrets() { exit 1 fi - for ns in "${NAMESPACE_CATALOGD}" "${NAMESPACE_OPERATOR}"; do + # OLM v1 catalogd and operator-controller authenticate via the global pull secret + # (openshift-config/pull-secret), not per-resource pullSecret fields. + # Merge internal registry credentials into the global pull secret. + local internal_registry_url="image-registry.openshift-image-registry.svc:5000" + local token + token=$(oc whoami -t) + local internal_auth + internal_auth=$(echo -n "kubeadmin:${token}" | base64 -w0) + + local existing_pull_secret + existing_pull_secret=$(oc get secret pull-secret -n openshift-config -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d) + + local merged + merged=$(echo "${existing_pull_secret}" | python3 -c " +import json, sys +data = json.load(sys.stdin) +data['auths']['${internal_registry_url}'] = {'auth': '${internal_auth}'} +data['auths']['$(buildRegistryUrl)'] = {'auth': '${internal_auth}'} +json.dump(data, sys.stdout) +") + + echo "${merged}" | oc set data secret/pull-secret -n openshift-config --from-file=.dockerconfigjson=/dev/stdin >&2 + infof "Merged internal registry credentials into global pull secret (openshift-config/pull-secret)" + + # Also create namespace-scoped secrets for the operator SA to pull images + for ns in "${NAMESPACE_OPERATOR}"; do if invoke_cluster_cli -n "${ns}" get secret internal-reg-ext-auth-for-rhdh &>/dev/null; then invoke_cluster_cli -n "${ns}" delete secret internal-reg-ext-auth-for-rhdh >&2 fi invoke_cluster_cli -n "${ns}" create secret docker-registry internal-reg-ext-auth-for-rhdh \ --docker-server="$(buildRegistryUrl)" \ --docker-username=kubeadmin \ - --docker-password="$(oc whoami -t)" \ + --docker-password="${token}" \ --docker-email="admin@internal-registry-ext.example.com" >&2 if invoke_cluster_cli -n "${ns}" get secret internal-reg-auth-for-rhdh &>/dev/null; then invoke_cluster_cli -n "${ns}" delete secret internal-reg-auth-for-rhdh >&2 fi invoke_cluster_cli -n "${ns}" create secret docker-registry internal-reg-auth-for-rhdh \ - --docker-server="image-registry.openshift-image-registry.svc:5000" \ + --docker-server="${internal_registry_url}" \ --docker-username=kubeadmin \ - --docker-password="$(oc whoami -t)" \ + --docker-password="${token}" \ --docker-email="admin@internal-registry.example.com" >&2 done @@ -1191,9 +1216,6 @@ EOF debugf "Processing native ClusterCatalog: ${manifest}" "$YQ" -i '.metadata.name = "rhdh-catalog"' "${manifest}" "$YQ" -i '.spec.source.image.ref |= sub("default-route-openshift-image-registry\.apps\.[^/]+", "image-registry.openshift-image-registry.svc:5000")' "${manifest}" - if [[ -n "${CATALOG_PULL_SECRET}" ]]; then - "$YQ" -i ".spec.source.image.pullSecret = \"${CATALOG_PULL_SECRET}\"" "${manifest}" - fi invoke_cluster_cli apply -f "${manifest}" foundClusterCatalog=true foundResources=true @@ -1217,7 +1239,6 @@ spec: type: Image image: ref: ${catalogImage} - pullSecret: ${CATALOG_PULL_SECRET} EOF foundResources=true fi @@ -1323,7 +1344,6 @@ spec: type: Image image: ref: ${my_operator_index} - pullSecret: ${CATALOG_PULL_SECRET} EOF fi From eaeaec48ce24164c00095c92acb3a14d8bb32eb2 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Wed, 8 Jul 2026 01:26:37 +0100 Subject: [PATCH 29/30] Fix ShellCheck warnings: remove unused CATALOG_PULL_SECRET and single-element loop Remove all CATALOG_PULL_SECRET assignments since the variable is no longer used after switching OLM v1 to global pull secret authentication. Also replace single-element for loop with direct variable usage (SC2066). Signed-off-by: Fortune Ndlovu --- .../scripts/prepare-restricted-environment.sh | 47 +++++++------------ 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 03bfeb654..148c0570c 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -19,7 +19,6 @@ INDEX_IMAGE="registry.redhat.io/redhat/redhat-operator-index:v4.18" FILTERED_VERSIONS=("*") OLM_VERSION="auto" RESOLVED_OLM_VERSION="" -CATALOG_PULL_SECRET="" NAMESPACE_CATALOGD="" # assume mikefarah version of yq is already available on the path; if 1, then install the version shown @@ -438,24 +437,22 @@ json.dump(data, sys.stdout) infof "Merged internal registry credentials into global pull secret (openshift-config/pull-secret)" # Also create namespace-scoped secrets for the operator SA to pull images - for ns in "${NAMESPACE_OPERATOR}"; do - if invoke_cluster_cli -n "${ns}" get secret internal-reg-ext-auth-for-rhdh &>/dev/null; then - invoke_cluster_cli -n "${ns}" delete secret internal-reg-ext-auth-for-rhdh >&2 - fi - invoke_cluster_cli -n "${ns}" create secret docker-registry internal-reg-ext-auth-for-rhdh \ - --docker-server="$(buildRegistryUrl)" \ - --docker-username=kubeadmin \ - --docker-password="${token}" \ - --docker-email="admin@internal-registry-ext.example.com" >&2 - if invoke_cluster_cli -n "${ns}" get secret internal-reg-auth-for-rhdh &>/dev/null; then - invoke_cluster_cli -n "${ns}" delete secret internal-reg-auth-for-rhdh >&2 - fi - invoke_cluster_cli -n "${ns}" create secret docker-registry internal-reg-auth-for-rhdh \ - --docker-server="${internal_registry_url}" \ - --docker-username=kubeadmin \ - --docker-password="${token}" \ - --docker-email="admin@internal-registry.example.com" >&2 - done + if invoke_cluster_cli -n "${NAMESPACE_OPERATOR}" get secret internal-reg-ext-auth-for-rhdh &>/dev/null; then + invoke_cluster_cli -n "${NAMESPACE_OPERATOR}" delete secret internal-reg-ext-auth-for-rhdh >&2 + fi + invoke_cluster_cli -n "${NAMESPACE_OPERATOR}" create secret docker-registry internal-reg-ext-auth-for-rhdh \ + --docker-server="$(buildRegistryUrl)" \ + --docker-username=kubeadmin \ + --docker-password="${token}" \ + --docker-email="admin@internal-registry-ext.example.com" >&2 + if invoke_cluster_cli -n "${NAMESPACE_OPERATOR}" get secret internal-reg-auth-for-rhdh &>/dev/null; then + invoke_cluster_cli -n "${NAMESPACE_OPERATOR}" delete secret internal-reg-auth-for-rhdh >&2 + fi + invoke_cluster_cli -n "${NAMESPACE_OPERATOR}" create secret docker-registry internal-reg-auth-for-rhdh \ + --docker-server="${internal_registry_url}" \ + --docker-username=kubeadmin \ + --docker-password="${token}" \ + --docker-email="admin@internal-registry.example.com" >&2 # Grant image-puller to catalogd service accounts so they can pull from the internal registry local catalogd_sa @@ -1042,11 +1039,6 @@ fi if [[ -n "${TO_REGISTRY}" ]]; then resolve_olm_version - if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then - CATALOG_PULL_SECRET="internal-reg-auth-for-rhdh" - else - CATALOG_PULL_SECRET="reg-pull-secret" - fi if [[ "${IS_OPENSHIFT}" = "true" && "${TO_REGISTRY}" = "OCP_INTERNAL" && "${RESOLVED_OLM_VERSION}" == "v1" ]]; then prepare_olm_v1_secrets fi @@ -1287,8 +1279,6 @@ else NAMESPACE_CATALOGSOURCE='$NAMESPACE_CATALOGSOURCE' # shellcheck disable=SC2016 my_operator_index='$CATALOG_IMAGE' - # shellcheck disable=SC2016 - CATALOG_PULL_SECRET='$CATALOG_PULL_SECRET' if [[ -n "${TO_REGISTRY}" ]]; then # It assumes that the user is also connected to a cluster detect_ocp_and_set_env_var @@ -1326,11 +1316,6 @@ else NAMESPACE_CATALOGSOURCE="olm" fi my_operator_index="$(buildCatalogImageUrl "internal")" - if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then - CATALOG_PULL_SECRET="internal-reg-auth-for-rhdh" - else - CATALOG_PULL_SECRET="reg-pull-secret" - fi fi if should_generate_v1_manifests; then From 7a37760cc08ce58648c67c779ae3feb2f402cea6 Mon Sep 17 00:00:00 2001 From: Fortune-Ndlovu Date: Wed, 8 Jul 2026 01:51:59 +0100 Subject: [PATCH 30/30] Grant image-puller in image source namespace for OLM v1 components The image-puller role bindings were being created in the catalogd SA's own namespace instead of the namespace where mirrored images are stored (e.g. rhdh). This caused authentication failures when catalogd and operator-controller tried to pull catalog and bundle images from the internal registry. Also add image-puller grants for the operator-controller service account, which needs to pull bundle images during ClusterExtension installation. Signed-off-by: Fortune Ndlovu --- .../scripts/prepare-restricted-environment.sh | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/.rhdh/scripts/prepare-restricted-environment.sh b/.rhdh/scripts/prepare-restricted-environment.sh index 148c0570c..34273bc10 100755 --- a/.rhdh/scripts/prepare-restricted-environment.sh +++ b/.rhdh/scripts/prepare-restricted-environment.sh @@ -454,15 +454,38 @@ json.dump(data, sys.stdout) --docker-password="${token}" \ --docker-email="admin@internal-registry.example.com" >&2 - # Grant image-puller to catalogd service accounts so they can pull from the internal registry + # Grant image-puller in the image source namespace so OLM v1 components can pull mirrored images. + # Images are mirrored under rhdh/ paths (e.g. rhdh/index, rhdh/rhdh-operator-bundle), so the + # OCP namespace where they're stored is derived from the catalog image path. + local catalog_image_url + catalog_image_url="$(buildCatalogImageUrl "internal")" + local registry_url + registry_url="$(buildRegistryUrl "internal")" + local image_path="${catalog_image_url#"${registry_url}"/}" + local image_namespace="${image_path%%/*}" + debugf "Granting image-puller in namespace '${image_namespace}' to OLM v1 service accounts" + local catalogd_sa catalogd_sa=$(invoke_cluster_cli get deployment -n "${NAMESPACE_CATALOGD}" -l 'app.kubernetes.io/name=catalogd' \ -o jsonpath='{.items[0].spec.template.spec.serviceAccountName}' 2>/dev/null || true) if [[ -z "${catalogd_sa}" ]]; then catalogd_sa="catalogd-controller-manager" fi - invoke_cluster_cli policy add-role-to-user system:image-puller "system:serviceaccount:${NAMESPACE_CATALOGD}:default" -n "${NAMESPACE_CATALOGD}" >&2 || true - invoke_cluster_cli policy add-role-to-user system:image-puller "system:serviceaccount:${NAMESPACE_CATALOGD}:${catalogd_sa}" -n "${NAMESPACE_CATALOGD}" >&2 || true + invoke_cluster_cli policy add-role-to-user system:image-puller "system:serviceaccount:${NAMESPACE_CATALOGD}:${catalogd_sa}" -n "${image_namespace}" >&2 || true + + local oc_ns + oc_ns=$(invoke_cluster_cli get deployment -A -l 'app.kubernetes.io/name=operator-controller' \ + -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true) + if [[ -z "${oc_ns}" ]]; then + oc_ns="openshift-operator-controller" + fi + local oc_sa + oc_sa=$(invoke_cluster_cli get deployment -n "${oc_ns}" -l 'app.kubernetes.io/name=operator-controller' \ + -o jsonpath='{.items[0].spec.template.spec.serviceAccountName}' 2>/dev/null || true) + if [[ -z "${oc_sa}" ]]; then + oc_sa="operator-controller-controller-manager" + fi + invoke_cluster_cli policy add-role-to-user system:image-puller "system:serviceaccount:${oc_ns}:${oc_sa}" -n "${image_namespace}" >&2 || true } # Generate v1 manifests when: