From dd1b040b14e6de715990934cc923a4f715446804 Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 11:24:29 +0300 Subject: [PATCH 01/10] OSAC-1338: Create upgrade playbook and migration framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add upgrade.yaml playbook and upgrade.sh script to handle migrations from previous Enclave versions to the current version. Framework structure: - upgrade.sh: Entry point script following sync.sh patterns with getValue(), step_done(), and timestamped logging - playbooks/upgrade.yaml: Main orchestration playbook that loads configuration and runs migrations - playbooks/tasks/migrations.yaml: Migration coordinator that conditionally includes migrations based on enabled_plugins - playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml: Reusable migration task for foundation plugins Initial migrations: - ODF operator subscription: core catalog → redhat-operator-index-odf - LVMS operator subscription: core catalog → redhat-operator-index-lvms Background: Previous Enclave versions mirrored foundation plugin operators as part of the core catalog source. The plugin framework now creates dedicated catalog sources per plugin (introduced in #420, #456, #457). This migration ensures existing deployments are updated to use the new plugin-specific catalog sources. The migration is idempotent and only runs when the respective plugin is enabled in the deployment configuration. Assisted-by: Claude Code --- playbooks/tasks/migrations.yaml | 26 +++++++++ .../foundation_plugin_catalog_source.yaml | 55 +++++++++++++++++++ playbooks/upgrade.yaml | 23 ++++++++ upgrade.sh | 30 ++++++++++ 4 files changed, 134 insertions(+) create mode 100644 playbooks/tasks/migrations.yaml create mode 100644 playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml create mode 100644 playbooks/upgrade.yaml create mode 100755 upgrade.sh diff --git a/playbooks/tasks/migrations.yaml b/playbooks/tasks/migrations.yaml new file mode 100644 index 00000000..e99d3ded --- /dev/null +++ b/playbooks/tasks/migrations.yaml @@ -0,0 +1,26 @@ +--- +# Red Hat Sovereign Enclave - Migrations +# +# This file contains all migration tasks for upgrading from previous Enclave versions. +# Called from upgrade.yaml via include_tasks. +# +# Individual migrations are organized under playbooks/tasks/migrations/ and are +# conditionally included based on enabled_plugins and other runtime conditions. + +- name: Migrate ODF subscription source + ansible.builtin.include_tasks: + file: migrations/foundation_plugin_catalog_source.yaml + vars: + plugin_name: odf + operator_name: odf-operator + operator_namespace: openshift-storage + when: "'odf' in enabled_plugins" + +- name: Migrate LVMS subscription source + ansible.builtin.include_tasks: + file: migrations/foundation_plugin_catalog_source.yaml + vars: + plugin_name: lvms + operator_name: lvms-operator + operator_namespace: openshift-storage + when: "'lvms' in enabled_plugins" diff --git a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml new file mode 100644 index 00000000..15e8528d --- /dev/null +++ b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml @@ -0,0 +1,55 @@ +--- +# Migrate foundation plugin subscription from core catalog source to plugin-specific catalog source +# +# Background: Previous Enclave versions mirrored foundation plugin operators as part +# of the core catalog source. The plugin framework now creates dedicated catalog +# sources per plugin (e.g., redhat-operator-index-odf for ODF). +# +# This migration patches the operator subscription to use the plugin-specific catalog source. +# +# Required variables: +# plugin_name: Name of the plugin (e.g., "odf", "lvms") +# operator_name: Name of the operator subscription (e.g., "odf-operator", "lvms-operator") +# operator_namespace: Namespace where the operator is installed (e.g., "openshift-storage") + +- name: Check if {{ plugin_name | upper }} subscription exists + kubernetes.core.k8s_info: + api_version: operators.coreos.com/v1alpha1 + kind: Subscription + name: "{{ operator_name }}" + namespace: "{{ operator_namespace }}" + environment: + KUBECONFIG: "{{ workingDir }}/ocp-cluster/auth/kubeconfig" + register: r_plugin_subscription + +- name: Patch {{ plugin_name | upper }} subscription to plugin-specific catalog source + kubernetes.core.k8s: + state: patched + api_version: operators.coreos.com/v1alpha1 + kind: Subscription + name: "{{ operator_name }}" + namespace: "{{ operator_namespace }}" + definition: + spec: + source: "redhat-operator-index-{{ plugin_name }}" + environment: + KUBECONFIG: "{{ workingDir }}/ocp-cluster/auth/kubeconfig" + register: r_plugin_patch + retries: "{{ k8s_retries }}" + delay: "{{ k8s_delay }}" + until: r_plugin_patch is success + when: + - r_plugin_subscription.resources | length > 0 + - r_plugin_subscription.resources[0].spec.source != "redhat-operator-index-{{ plugin_name }}" + +- name: Display {{ plugin_name | upper }} migration status + ansible.builtin.debug: + msg: >- + {% if r_plugin_subscription.resources | length == 0 %} + {{ plugin_name | upper }} subscription not found — no migration needed + {% elif r_plugin_subscription.resources[0].spec.source == "redhat-operator-index-" + plugin_name %} + {{ plugin_name | upper }} subscription already using plugin-specific catalog source — no migration needed + {% else %} + {{ plugin_name | upper }} subscription migrated from {{ r_plugin_subscription.resources[0].spec.source }} + to redhat-operator-index-{{ plugin_name }} + {% endif %} diff --git a/playbooks/upgrade.yaml b/playbooks/upgrade.yaml new file mode 100644 index 00000000..a9b1b64e --- /dev/null +++ b/playbooks/upgrade.yaml @@ -0,0 +1,23 @@ +--- +# Red Hat Sovereign Enclave - Upgrade Playbook +# +# This playbook handles upgrades from previous Enclave versions to ensure +# compatibility with the current version. +# +# Usage: +# ansible-playbook playbooks/upgrade.yaml -e workingDir=/home/cloud-user + +- name: Upgrade Enclave deployment + hosts: localhost + gather_facts: false + vars: + k8s_retries: 30 + k8s_delay: 10 + tasks: + - name: Load configuration + ansible.builtin.include_tasks: + file: common/load-vars.yaml + + - name: Run migrations + ansible.builtin.include_tasks: + file: tasks/migrations.yaml diff --git a/upgrade.sh b/upgrade.sh new file mode 100755 index 00000000..f1d9239b --- /dev/null +++ b/upgrade.sh @@ -0,0 +1,30 @@ +#!/bin/bash -e +set -o pipefail +set -e + +# Upgrade script for Red Hat Sovereign Enclave +# Run sync.sh BEFORE running this script to ensure content is synchronized + +global_vars=config/global.yaml + +getValue(){ + python -c 'import sys, yaml, json; print(json.dumps(yaml.safe_load(sys.stdin)))' < $global_vars \ + | jq -r $1 +} + +step_done(){ + echo -e "\e[38;5;10m Done...\033[0m" | tee -a ${log} + date | tee -a ${log} +} + +workingDir=$(getValue .workingDir) +DSTAMP=$(date +%Y%m%d_%H%M%S) +logdir=${workingDir}/logs +log="$logdir/${DSTAMP}-upgrade" + +mkdir -p "$(dirname $log)" +date > "$log" + +echo "Running Enclave upgrade migrations .. " | tee -a ${log} + ANSIBLE_LOG_PATH=${log} ansible-playbook playbooks/upgrade.yaml -e fresh=false +step_done From 3eee3df092016ae46fb17550deb732a4d96687e8 Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 11:28:37 +0300 Subject: [PATCH 02/10] Add catalog_mirror as input variable to foundation plugin migration The catalog source name is constructed as {catalog_mirror}-{plugin_name}, where catalog_mirror can be either mirror_rh_operator_catalog (mirror-redhat-operators) or mirror_certified_rh_operator_catalog (mirror-certified-operators) depending on the plugin's catalog type. This matches the catalog source naming in deploy_plugin.yaml: - catalog_mirror: mirror-redhat-operators (default) - catalog_mirror: mirror-certified-operators (when plugin.catalog == certified) Both ODF and LVMS use the default redhat catalog, so they use mirror-redhat-operators as the catalog_mirror prefix. Assisted-by: Claude Code --- playbooks/tasks/migrations.yaml | 2 ++ .../migrations/foundation_plugin_catalog_source.yaml | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/playbooks/tasks/migrations.yaml b/playbooks/tasks/migrations.yaml index e99d3ded..f6814d00 100644 --- a/playbooks/tasks/migrations.yaml +++ b/playbooks/tasks/migrations.yaml @@ -14,6 +14,7 @@ plugin_name: odf operator_name: odf-operator operator_namespace: openshift-storage + catalog_mirror: "{{ mirror_rh_operator_catalog }}" when: "'odf' in enabled_plugins" - name: Migrate LVMS subscription source @@ -23,4 +24,5 @@ plugin_name: lvms operator_name: lvms-operator operator_namespace: openshift-storage + catalog_mirror: "{{ mirror_rh_operator_catalog }}" when: "'lvms' in enabled_plugins" diff --git a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml index 15e8528d..69b8a602 100644 --- a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml +++ b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml @@ -3,7 +3,7 @@ # # Background: Previous Enclave versions mirrored foundation plugin operators as part # of the core catalog source. The plugin framework now creates dedicated catalog -# sources per plugin (e.g., redhat-operator-index-odf for ODF). +# sources per plugin (e.g., mirror-redhat-operators-odf for ODF). # # This migration patches the operator subscription to use the plugin-specific catalog source. # @@ -11,6 +11,7 @@ # plugin_name: Name of the plugin (e.g., "odf", "lvms") # operator_name: Name of the operator subscription (e.g., "odf-operator", "lvms-operator") # operator_namespace: Namespace where the operator is installed (e.g., "openshift-storage") +# catalog_mirror: Catalog mirror prefix (e.g., "mirror-redhat-operators" or "mirror-certified-operators") - name: Check if {{ plugin_name | upper }} subscription exists kubernetes.core.k8s_info: @@ -31,7 +32,7 @@ namespace: "{{ operator_namespace }}" definition: spec: - source: "redhat-operator-index-{{ plugin_name }}" + source: "{{ catalog_mirror }}-{{ plugin_name }}" environment: KUBECONFIG: "{{ workingDir }}/ocp-cluster/auth/kubeconfig" register: r_plugin_patch @@ -40,16 +41,16 @@ until: r_plugin_patch is success when: - r_plugin_subscription.resources | length > 0 - - r_plugin_subscription.resources[0].spec.source != "redhat-operator-index-{{ plugin_name }}" + - r_plugin_subscription.resources[0].spec.source != catalog_mirror + "-" + plugin_name - name: Display {{ plugin_name | upper }} migration status ansible.builtin.debug: msg: >- {% if r_plugin_subscription.resources | length == 0 %} {{ plugin_name | upper }} subscription not found — no migration needed - {% elif r_plugin_subscription.resources[0].spec.source == "redhat-operator-index-" + plugin_name %} + {% elif r_plugin_subscription.resources[0].spec.source == catalog_mirror + "-" + plugin_name %} {{ plugin_name | upper }} subscription already using plugin-specific catalog source — no migration needed {% else %} {{ plugin_name | upper }} subscription migrated from {{ r_plugin_subscription.resources[0].spec.source }} - to redhat-operator-index-{{ plugin_name }} + to {{ catalog_mirror }}-{{ plugin_name }} {% endif %} From ffd3cd421ce0ef0974748e70f8843946148b055a Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 13:05:30 +0300 Subject: [PATCH 03/10] Address CodeRabbit review comments 1. upgrade.sh: Add set -u (nounset) and quote all variable expansions - Changed from 'set -e' + 'set -o pipefail' to 'set -euo pipefail' - Quoted all variable expansions to handle paths with spaces 2. migrations.yaml: Add default([]) to enabled_plugins checks - Prevents failure if enabled_plugins is undefined - Makes the playbook rerunnable for older/partial configs 3. foundation_plugin_catalog_source.yaml: Add safety checks - Verify spec.source is defined before comparison - Only migrate subscriptions from known core/legacy catalogs - Pattern matches: redhat-operators, cs-*, or catalog_mirror prefix - Prevents accidental overwrite of custom catalog sources Assisted-by: Claude Code --- playbooks/tasks/migrations.yaml | 4 ++-- .../foundation_plugin_catalog_source.yaml | 2 ++ upgrade.sh | 19 +++++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/playbooks/tasks/migrations.yaml b/playbooks/tasks/migrations.yaml index f6814d00..84b5ae47 100644 --- a/playbooks/tasks/migrations.yaml +++ b/playbooks/tasks/migrations.yaml @@ -15,7 +15,7 @@ operator_name: odf-operator operator_namespace: openshift-storage catalog_mirror: "{{ mirror_rh_operator_catalog }}" - when: "'odf' in enabled_plugins" + when: "'odf' in (enabled_plugins | default([]))" - name: Migrate LVMS subscription source ansible.builtin.include_tasks: @@ -25,4 +25,4 @@ operator_name: lvms-operator operator_namespace: openshift-storage catalog_mirror: "{{ mirror_rh_operator_catalog }}" - when: "'lvms' in enabled_plugins" + when: "'lvms' in (enabled_plugins | default([]))" diff --git a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml index 69b8a602..af732646 100644 --- a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml +++ b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml @@ -41,7 +41,9 @@ until: r_plugin_patch is success when: - r_plugin_subscription.resources | length > 0 + - r_plugin_subscription.resources[0].spec.source is defined - r_plugin_subscription.resources[0].spec.source != catalog_mirror + "-" + plugin_name + - r_plugin_subscription.resources[0].spec.source is match("^(redhat-operators|cs-.*|" + catalog_mirror + ")$") - name: Display {{ plugin_name | upper }} migration status ansible.builtin.debug: diff --git a/upgrade.sh b/upgrade.sh index f1d9239b..b2200c78 100755 --- a/upgrade.sh +++ b/upgrade.sh @@ -1,6 +1,5 @@ -#!/bin/bash -e -set -o pipefail -set -e +#!/bin/bash +set -euo pipefail # Upgrade script for Red Hat Sovereign Enclave # Run sync.sh BEFORE running this script to ensure content is synchronized @@ -8,13 +7,13 @@ set -e global_vars=config/global.yaml getValue(){ - python -c 'import sys, yaml, json; print(json.dumps(yaml.safe_load(sys.stdin)))' < $global_vars \ - | jq -r $1 + python -c 'import sys, yaml, json; print(json.dumps(yaml.safe_load(sys.stdin)))' < "$global_vars" \ + | jq -r "$1" } step_done(){ - echo -e "\e[38;5;10m Done...\033[0m" | tee -a ${log} - date | tee -a ${log} + echo -e "\e[38;5;10m Done...\033[0m" | tee -a "${log}" + date | tee -a "${log}" } workingDir=$(getValue .workingDir) @@ -22,9 +21,9 @@ DSTAMP=$(date +%Y%m%d_%H%M%S) logdir=${workingDir}/logs log="$logdir/${DSTAMP}-upgrade" -mkdir -p "$(dirname $log)" +mkdir -p "$(dirname "$log")" date > "$log" -echo "Running Enclave upgrade migrations .. " | tee -a ${log} - ANSIBLE_LOG_PATH=${log} ansible-playbook playbooks/upgrade.yaml -e fresh=false +echo "Running Enclave upgrade migrations .. " | tee -a "${log}" + ANSIBLE_LOG_PATH="${log}" ansible-playbook playbooks/upgrade.yaml -e fresh=false step_done From f55292212fa59601f6d41bf2963977a3be36e031 Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 13:07:07 +0300 Subject: [PATCH 04/10] Restrict foundation plugin migrations to disconnected mode only Foundation plugin catalog source migrations only apply to disconnected deployments where catalog sources are mirrored locally. Connected deployments use upstream Red Hat catalogs directly and don't need migration. Added disconnected check to both ODF and LVMS migration tasks. Assisted-by: Claude Code --- playbooks/tasks/migrations.yaml | 8 ++++++-- .../migrations/foundation_plugin_catalog_source.yaml | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/playbooks/tasks/migrations.yaml b/playbooks/tasks/migrations.yaml index 84b5ae47..6a7b2186 100644 --- a/playbooks/tasks/migrations.yaml +++ b/playbooks/tasks/migrations.yaml @@ -15,7 +15,9 @@ operator_name: odf-operator operator_namespace: openshift-storage catalog_mirror: "{{ mirror_rh_operator_catalog }}" - when: "'odf' in (enabled_plugins | default([]))" + when: + - disconnected | default(true) | bool + - "'odf' in (enabled_plugins | default([]))" - name: Migrate LVMS subscription source ansible.builtin.include_tasks: @@ -25,4 +27,6 @@ operator_name: lvms-operator operator_namespace: openshift-storage catalog_mirror: "{{ mirror_rh_operator_catalog }}" - when: "'lvms' in (enabled_plugins | default([]))" + when: + - disconnected | default(true) | bool + - "'lvms' in (enabled_plugins | default([]))" diff --git a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml index af732646..4d13e2ac 100644 --- a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml +++ b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml @@ -7,6 +7,9 @@ # # This migration patches the operator subscription to use the plugin-specific catalog source. # +# Note: This migration only applies to disconnected deployments where catalog sources +# are mirrored locally. Connected deployments use upstream Red Hat catalogs directly. +# # Required variables: # plugin_name: Name of the plugin (e.g., "odf", "lvms") # operator_name: Name of the operator subscription (e.g., "odf-operator", "lvms-operator") From 3c22ab4c4d2ce66a38189d1e7eb55387f1f55f42 Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 13:07:25 +0300 Subject: [PATCH 05/10] Fix legacy catalog source check to exact match The legacy catalog source is exactly catalog_mirror (e.g., "mirror-redhat-operators"), not a pattern. Changed from regex pattern matching to exact equality check. This ensures we only migrate subscriptions that are using the exact legacy core catalog source, not custom or other catalog sources. Assisted-by: Claude Code --- .../tasks/migrations/foundation_plugin_catalog_source.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml index 4d13e2ac..0e944bcf 100644 --- a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml +++ b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml @@ -46,7 +46,7 @@ - r_plugin_subscription.resources | length > 0 - r_plugin_subscription.resources[0].spec.source is defined - r_plugin_subscription.resources[0].spec.source != catalog_mirror + "-" + plugin_name - - r_plugin_subscription.resources[0].spec.source is match("^(redhat-operators|cs-.*|" + catalog_mirror + ")$") + - r_plugin_subscription.resources[0].spec.source == catalog_mirror - name: Display {{ plugin_name | upper }} migration status ansible.builtin.debug: From d9a3337d4a18867d5b5458369d05751058b1404c Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 14:23:26 +0300 Subject: [PATCH 06/10] Generalize foundation plugin catalog source migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hardcoded ODF and LVMS migration blocks with dynamic discovery that automatically handles ALL foundation plugins. Changes: - Discover all foundation plugins using the same pattern as collect_core_plugin_operators.yaml - Loop over each foundation plugin's operators using subelements() - Automatically determine catalog_mirror based on plugin.catalog field: - redhat (default) → mirror_rh_operator_catalog - certified → mirror_certified_rh_operator_catalog - Skip operators with explicit operator.source defined Benefits: - Extensible: New foundation plugins automatically get migrated without code changes - Maintainable: Follows existing Enclave patterns for plugin discovery - Safe: Multiple guards (disconnected mode, enabled plugins, no custom source) - DRY: No hardcoded plugin names Example migration flow for ODF, LVMS, VAST-CSI: - odf/odf-operator: mirror-redhat-operators → mirror-redhat-operators-odf - lvms/lvms-operator: mirror-redhat-operators → mirror-redhat-operators-lvms - vast-csi/vast-csi-operator: mirror-certified-operators → mirror-certified-operators-vast-csi Assisted-by: Claude Code --- playbooks/tasks/migrations.yaml | 68 ++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/playbooks/tasks/migrations.yaml b/playbooks/tasks/migrations.yaml index 6a7b2186..68bfe298 100644 --- a/playbooks/tasks/migrations.yaml +++ b/playbooks/tasks/migrations.yaml @@ -7,26 +7,60 @@ # Individual migrations are organized under playbooks/tasks/migrations/ and are # conditionally included based on enabled_plugins and other runtime conditions. -- name: Migrate ODF subscription source - ansible.builtin.include_tasks: - file: migrations/foundation_plugin_catalog_source.yaml - vars: - plugin_name: odf - operator_name: odf-operator - operator_namespace: openshift-storage - catalog_mirror: "{{ mirror_rh_operator_catalog }}" - when: - - disconnected | default(true) | bool - - "'odf' in (enabled_plugins | default([]))" +# ============================================================================== +# Foundation Plugin Catalog Source Migration +# +# Migrates foundation plugin operator subscriptions from legacy core catalog +# sources to plugin-specific catalog sources. +# +# Only applies to disconnected deployments and operators without explicit +# operator.source definitions in their plugin descriptors. +# ============================================================================== + +- name: Find plugin descriptors + ansible.builtin.find: + paths: "{{ playbook_dir }}/../plugins" + patterns: "plugin.yaml" + recurse: true + depth: 2 + register: r_mig_find + when: disconnected | default(true) | bool + +- name: Read plugin descriptors + ansible.builtin.slurp: + src: "{{ plugin_path }}" + loop: "{{ r_mig_find.files | default([]) | map(attribute='path') | list }}" + register: r_mig_slurp + loop_control: + loop_var: plugin_path + when: disconnected | default(true) | bool + +- name: Filter foundation plugins + ansible.builtin.set_fact: + _foundation_plugins: >- + {{ r_mig_slurp.results | default([]) + | map(attribute='content') + | map('b64decode') + | map('from_yaml') + | selectattr('type', 'equalto', 'foundation') + | selectattr('name', 'in', enabled_plugins | default([])) + | list }} + when: disconnected | default(true) | bool -- name: Migrate LVMS subscription source +- name: Migrate foundation plugin operator subscriptions ansible.builtin.include_tasks: file: migrations/foundation_plugin_catalog_source.yaml vars: - plugin_name: lvms - operator_name: lvms-operator - operator_namespace: openshift-storage - catalog_mirror: "{{ mirror_rh_operator_catalog }}" + plugin: "{{ plugin_operator.0 }}" + operator: "{{ plugin_operator.1 }}" + plugin_name: "{{ plugin.name }}" + operator_name: "{{ operator.name }}" + operator_namespace: "{{ operator.namespace }}" + catalog_mirror: "{{ mirror_certified_rh_operator_catalog if (plugin.catalog | default('redhat')) == 'certified' else mirror_rh_operator_catalog }}" + loop: "{{ _foundation_plugins | default([]) | subelements('operators', skip_missing=True) }}" + loop_control: + loop_var: plugin_operator + label: "{{ plugin_operator.0.name }}/{{ plugin_operator.1.name }}" when: - disconnected | default(true) | bool - - "'lvms' in (enabled_plugins | default([]))" + - operator.source is not defined From 397112b56c57c1819f338905b0880c81ce017309 Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 15:20:49 +0300 Subject: [PATCH 07/10] Fix catalog source naming to include cs- prefix and version Catalog sources are named cs-{catalog_mirror}-{plugin_name}-v4-20, not just {catalog_mirror}-{plugin_name}. Legacy source: cs-mirror-redhat-operators-v4-20 New source: cs-mirror-redhat-operators-odf-v4-20 This matches the naming in deploy_plugin.yaml line 191: default_operator_source: "cs-{{ catalog_mirror }}-v4-20" Updated: - New source pattern to include cs- prefix and -v4-20 suffix - Legacy source check to exact match: cs-{catalog_mirror}-v4-20 - Documentation to reflect correct naming Assisted-by: Claude Code --- .../foundation_plugin_catalog_source.yaml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml index 0e944bcf..2240d245 100644 --- a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml +++ b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml @@ -2,8 +2,9 @@ # Migrate foundation plugin subscription from core catalog source to plugin-specific catalog source # # Background: Previous Enclave versions mirrored foundation plugin operators as part -# of the core catalog source. The plugin framework now creates dedicated catalog -# sources per plugin (e.g., mirror-redhat-operators-odf for ODF). +# of the core catalog source (e.g., cs-mirror-redhat-operators-v4-20). The plugin +# framework now creates dedicated catalog sources per plugin (e.g., +# cs-mirror-redhat-operators-odf-v4-20 for ODF). # # This migration patches the operator subscription to use the plugin-specific catalog source. # @@ -35,7 +36,7 @@ namespace: "{{ operator_namespace }}" definition: spec: - source: "{{ catalog_mirror }}-{{ plugin_name }}" + source: "cs-{{ catalog_mirror }}-{{ plugin_name }}-v4-20" environment: KUBECONFIG: "{{ workingDir }}/ocp-cluster/auth/kubeconfig" register: r_plugin_patch @@ -45,17 +46,17 @@ when: - r_plugin_subscription.resources | length > 0 - r_plugin_subscription.resources[0].spec.source is defined - - r_plugin_subscription.resources[0].spec.source != catalog_mirror + "-" + plugin_name - - r_plugin_subscription.resources[0].spec.source == catalog_mirror + - r_plugin_subscription.resources[0].spec.source != "cs-" + catalog_mirror + "-" + plugin_name + "-v4-20" + - r_plugin_subscription.resources[0].spec.source == "cs-" + catalog_mirror + "-v4-20" - name: Display {{ plugin_name | upper }} migration status ansible.builtin.debug: msg: >- {% if r_plugin_subscription.resources | length == 0 %} {{ plugin_name | upper }} subscription not found — no migration needed - {% elif r_plugin_subscription.resources[0].spec.source == catalog_mirror + "-" + plugin_name %} + {% elif r_plugin_subscription.resources[0].spec.source == "cs-" + catalog_mirror + "-" + plugin_name + "-v4-20" %} {{ plugin_name | upper }} subscription already using plugin-specific catalog source — no migration needed {% else %} {{ plugin_name | upper }} subscription migrated from {{ r_plugin_subscription.resources[0].spec.source }} - to {{ catalog_mirror }}-{{ plugin_name }} + to cs-{{ catalog_mirror }}-{{ plugin_name }}-v4-20 {% endif %} From f085edb8bab4ef91666f59d9e34ee875a10d4ac4 Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 15:31:45 +0300 Subject: [PATCH 08/10] Refactor: Rename and generalize migration task file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename foundation_plugin_catalog_source.yaml to operator_catalog_source.yaml and remove all plugin-specific references to make it a truly generic task. Changes: 1. Renamed file (git history preserved): - foundation_plugin_catalog_source.yaml → operator_catalog_source.yaml 2. Made operator_catalog_source.yaml generic: - Removed plugin-specific variable names (plugin_name, catalog_mirror) - Added generic variables: legacy_source, new_source - Changed variable names: r_plugin_* → r_operator_* - Simplified header to reflect generic nature 3. Updated migrations.yaml to compute sources: - Computes legacy_source and new_source before calling the task - Passes pre-computed source strings to the generic task - Keeps all plugin-specific logic (discovery, filtering, catalog determination) in migrations.yaml Benefits: - Clear separation of concerns: plugin logic vs. generic patching - operator_catalog_source.yaml is 100% reusable for any operator subscription migration - More explicit: legacy_source and new_source are clearer than implicit string construction - Better naming: file reflects what it does, not who calls it Assisted-by: Claude Code --- playbooks/tasks/migrations.yaml | 7 ++- .../foundation_plugin_catalog_source.yaml | 62 ------------------- .../migrations/operator_catalog_source.yaml | 56 +++++++++++++++++ 3 files changed, 60 insertions(+), 65 deletions(-) delete mode 100644 playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml create mode 100644 playbooks/tasks/migrations/operator_catalog_source.yaml diff --git a/playbooks/tasks/migrations.yaml b/playbooks/tasks/migrations.yaml index 68bfe298..fa12dcb4 100644 --- a/playbooks/tasks/migrations.yaml +++ b/playbooks/tasks/migrations.yaml @@ -49,14 +49,15 @@ - name: Migrate foundation plugin operator subscriptions ansible.builtin.include_tasks: - file: migrations/foundation_plugin_catalog_source.yaml + file: migrations/operator_catalog_source.yaml vars: plugin: "{{ plugin_operator.0 }}" operator: "{{ plugin_operator.1 }}" - plugin_name: "{{ plugin.name }}" + catalog_mirror: "{{ mirror_certified_rh_operator_catalog if (plugin.catalog | default('redhat')) == 'certified' else mirror_rh_operator_catalog }}" operator_name: "{{ operator.name }}" operator_namespace: "{{ operator.namespace }}" - catalog_mirror: "{{ mirror_certified_rh_operator_catalog if (plugin.catalog | default('redhat')) == 'certified' else mirror_rh_operator_catalog }}" + legacy_source: "cs-{{ catalog_mirror }}-v4-20" + new_source: "cs-{{ catalog_mirror }}-{{ plugin.name }}-v4-20" loop: "{{ _foundation_plugins | default([]) | subelements('operators', skip_missing=True) }}" loop_control: loop_var: plugin_operator diff --git a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml b/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml deleted file mode 100644 index 2240d245..00000000 --- a/playbooks/tasks/migrations/foundation_plugin_catalog_source.yaml +++ /dev/null @@ -1,62 +0,0 @@ ---- -# Migrate foundation plugin subscription from core catalog source to plugin-specific catalog source -# -# Background: Previous Enclave versions mirrored foundation plugin operators as part -# of the core catalog source (e.g., cs-mirror-redhat-operators-v4-20). The plugin -# framework now creates dedicated catalog sources per plugin (e.g., -# cs-mirror-redhat-operators-odf-v4-20 for ODF). -# -# This migration patches the operator subscription to use the plugin-specific catalog source. -# -# Note: This migration only applies to disconnected deployments where catalog sources -# are mirrored locally. Connected deployments use upstream Red Hat catalogs directly. -# -# Required variables: -# plugin_name: Name of the plugin (e.g., "odf", "lvms") -# operator_name: Name of the operator subscription (e.g., "odf-operator", "lvms-operator") -# operator_namespace: Namespace where the operator is installed (e.g., "openshift-storage") -# catalog_mirror: Catalog mirror prefix (e.g., "mirror-redhat-operators" or "mirror-certified-operators") - -- name: Check if {{ plugin_name | upper }} subscription exists - kubernetes.core.k8s_info: - api_version: operators.coreos.com/v1alpha1 - kind: Subscription - name: "{{ operator_name }}" - namespace: "{{ operator_namespace }}" - environment: - KUBECONFIG: "{{ workingDir }}/ocp-cluster/auth/kubeconfig" - register: r_plugin_subscription - -- name: Patch {{ plugin_name | upper }} subscription to plugin-specific catalog source - kubernetes.core.k8s: - state: patched - api_version: operators.coreos.com/v1alpha1 - kind: Subscription - name: "{{ operator_name }}" - namespace: "{{ operator_namespace }}" - definition: - spec: - source: "cs-{{ catalog_mirror }}-{{ plugin_name }}-v4-20" - environment: - KUBECONFIG: "{{ workingDir }}/ocp-cluster/auth/kubeconfig" - register: r_plugin_patch - retries: "{{ k8s_retries }}" - delay: "{{ k8s_delay }}" - until: r_plugin_patch is success - when: - - r_plugin_subscription.resources | length > 0 - - r_plugin_subscription.resources[0].spec.source is defined - - r_plugin_subscription.resources[0].spec.source != "cs-" + catalog_mirror + "-" + plugin_name + "-v4-20" - - r_plugin_subscription.resources[0].spec.source == "cs-" + catalog_mirror + "-v4-20" - -- name: Display {{ plugin_name | upper }} migration status - ansible.builtin.debug: - msg: >- - {% if r_plugin_subscription.resources | length == 0 %} - {{ plugin_name | upper }} subscription not found — no migration needed - {% elif r_plugin_subscription.resources[0].spec.source == "cs-" + catalog_mirror + "-" + plugin_name + "-v4-20" %} - {{ plugin_name | upper }} subscription already using plugin-specific catalog source — no migration needed - {% else %} - {{ plugin_name | upper }} subscription migrated from {{ r_plugin_subscription.resources[0].spec.source }} - to cs-{{ catalog_mirror }}-{{ plugin_name }}-v4-20 - {% endif %} diff --git a/playbooks/tasks/migrations/operator_catalog_source.yaml b/playbooks/tasks/migrations/operator_catalog_source.yaml new file mode 100644 index 00000000..1af0789f --- /dev/null +++ b/playbooks/tasks/migrations/operator_catalog_source.yaml @@ -0,0 +1,56 @@ +--- +# Migrate operator subscription catalog source +# +# This is a generic task that patches an operator subscription's catalog source. +# All context-specific logic (discovery, filtering, catalog determination) is +# handled by the calling playbook. +# +# Required variables: +# operator_name: Name of the operator subscription (e.g., "odf-operator", "lvms-operator") +# operator_namespace: Namespace where the operator is installed (e.g., "openshift-storage") +# legacy_source: Expected legacy catalog source (e.g., "cs-mirror-redhat-operators-v4-20") +# new_source: New catalog source to migrate to (e.g., "cs-mirror-redhat-operators-odf-v4-20") + +- name: Check if {{ operator_name }} subscription exists + kubernetes.core.k8s_info: + api_version: operators.coreos.com/v1alpha1 + kind: Subscription + name: "{{ operator_name }}" + namespace: "{{ operator_namespace }}" + environment: + KUBECONFIG: "{{ workingDir }}/ocp-cluster/auth/kubeconfig" + register: r_operator_subscription + +- name: Patch {{ operator_name }} subscription catalog source + kubernetes.core.k8s: + state: patched + api_version: operators.coreos.com/v1alpha1 + kind: Subscription + name: "{{ operator_name }}" + namespace: "{{ operator_namespace }}" + definition: + spec: + source: "{{ new_source }}" + environment: + KUBECONFIG: "{{ workingDir }}/ocp-cluster/auth/kubeconfig" + register: r_operator_patch + retries: "{{ k8s_retries }}" + delay: "{{ k8s_delay }}" + until: r_operator_patch is success + when: + - r_operator_subscription.resources | length > 0 + - r_operator_subscription.resources[0].spec.source is defined + - r_operator_subscription.resources[0].spec.source != new_source + - r_operator_subscription.resources[0].spec.source == legacy_source + +- name: Display {{ operator_name }} migration status + ansible.builtin.debug: + msg: >- + {% if r_operator_subscription.resources | length == 0 %} + {{ operator_name }} subscription not found — no migration needed + {% elif r_operator_subscription.resources[0].spec.source == new_source %} + {{ operator_name }} subscription already using new catalog source — no migration needed + {% else %} + {{ operator_name }} subscription migrated from {{ r_operator_subscription.resources[0].spec.source }} + to {{ new_source }} + {% endif %} From d4a18f6a790d42f3be8e8b0144a407c3251b6cca Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 15:38:38 +0300 Subject: [PATCH 09/10] Extract foundation plugin catalog source migration to separate file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move foundation plugin catalog source migration logic from migrations.yaml into its own dedicated file for better organization. Changes: - Created foundation_plugin_catalog_sources.yaml with: - Plugin discovery (find, load, filter foundation plugins) - Catalog mirror determination (redhat vs certified) - Operator subscription migration loop - Simplified migrations.yaml to just: - Include foundation_plugin_catalog_sources.yaml Benefits: - migrations.yaml becomes a clean registry/index of all migrations - Each migration is self-contained in its own file - Easier to add new migrations (just create new file and include it) - Better organization as migrations grow File structure: playbooks/tasks/migrations/ ├── foundation_plugin_catalog_sources.yaml (specific migration) ├── operator_catalog_source.yaml (generic utility) └── (future migrations can be added here) Assisted-by: Claude Code --- playbooks/tasks/migrations.yaml | 59 +----------------- .../foundation_plugin_catalog_sources.yaml | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+), 57 deletions(-) create mode 100644 playbooks/tasks/migrations/foundation_plugin_catalog_sources.yaml diff --git a/playbooks/tasks/migrations.yaml b/playbooks/tasks/migrations.yaml index fa12dcb4..ff2b8770 100644 --- a/playbooks/tasks/migrations.yaml +++ b/playbooks/tasks/migrations.yaml @@ -7,61 +7,6 @@ # Individual migrations are organized under playbooks/tasks/migrations/ and are # conditionally included based on enabled_plugins and other runtime conditions. -# ============================================================================== -# Foundation Plugin Catalog Source Migration -# -# Migrates foundation plugin operator subscriptions from legacy core catalog -# sources to plugin-specific catalog sources. -# -# Only applies to disconnected deployments and operators without explicit -# operator.source definitions in their plugin descriptors. -# ============================================================================== - -- name: Find plugin descriptors - ansible.builtin.find: - paths: "{{ playbook_dir }}/../plugins" - patterns: "plugin.yaml" - recurse: true - depth: 2 - register: r_mig_find - when: disconnected | default(true) | bool - -- name: Read plugin descriptors - ansible.builtin.slurp: - src: "{{ plugin_path }}" - loop: "{{ r_mig_find.files | default([]) | map(attribute='path') | list }}" - register: r_mig_slurp - loop_control: - loop_var: plugin_path - when: disconnected | default(true) | bool - -- name: Filter foundation plugins - ansible.builtin.set_fact: - _foundation_plugins: >- - {{ r_mig_slurp.results | default([]) - | map(attribute='content') - | map('b64decode') - | map('from_yaml') - | selectattr('type', 'equalto', 'foundation') - | selectattr('name', 'in', enabled_plugins | default([])) - | list }} - when: disconnected | default(true) | bool - -- name: Migrate foundation plugin operator subscriptions +- name: Foundation plugin catalog source migration ansible.builtin.include_tasks: - file: migrations/operator_catalog_source.yaml - vars: - plugin: "{{ plugin_operator.0 }}" - operator: "{{ plugin_operator.1 }}" - catalog_mirror: "{{ mirror_certified_rh_operator_catalog if (plugin.catalog | default('redhat')) == 'certified' else mirror_rh_operator_catalog }}" - operator_name: "{{ operator.name }}" - operator_namespace: "{{ operator.namespace }}" - legacy_source: "cs-{{ catalog_mirror }}-v4-20" - new_source: "cs-{{ catalog_mirror }}-{{ plugin.name }}-v4-20" - loop: "{{ _foundation_plugins | default([]) | subelements('operators', skip_missing=True) }}" - loop_control: - loop_var: plugin_operator - label: "{{ plugin_operator.0.name }}/{{ plugin_operator.1.name }}" - when: - - disconnected | default(true) | bool - - operator.source is not defined + file: migrations/foundation_plugin_catalog_sources.yaml diff --git a/playbooks/tasks/migrations/foundation_plugin_catalog_sources.yaml b/playbooks/tasks/migrations/foundation_plugin_catalog_sources.yaml new file mode 100644 index 00000000..8bd939a5 --- /dev/null +++ b/playbooks/tasks/migrations/foundation_plugin_catalog_sources.yaml @@ -0,0 +1,62 @@ +--- +# Foundation Plugin Catalog Source Migration +# +# Migrates foundation plugin operator subscriptions from legacy core catalog +# sources to plugin-specific catalog sources. +# +# Background: Previous Enclave versions mirrored foundation plugin operators +# as part of the core catalog source (e.g., cs-mirror-redhat-operators-v4-20). +# The plugin framework now creates dedicated catalog sources per plugin (e.g., +# cs-mirror-redhat-operators-odf-v4-20 for ODF). +# +# Only applies to disconnected deployments and operators without explicit +# operator.source definitions in their plugin descriptors. + +- name: Find plugin descriptors + ansible.builtin.find: + paths: "{{ playbook_dir }}/../plugins" + patterns: "plugin.yaml" + recurse: true + depth: 2 + register: r_mig_find + when: disconnected | default(true) | bool + +- name: Read plugin descriptors + ansible.builtin.slurp: + src: "{{ plugin_path }}" + loop: "{{ r_mig_find.files | default([]) | map(attribute='path') | list }}" + register: r_mig_slurp + loop_control: + loop_var: plugin_path + when: disconnected | default(true) | bool + +- name: Filter foundation plugins + ansible.builtin.set_fact: + _foundation_plugins: >- + {{ r_mig_slurp.results | default([]) + | map(attribute='content') + | map('b64decode') + | map('from_yaml') + | selectattr('type', 'equalto', 'foundation') + | selectattr('name', 'in', enabled_plugins | default([])) + | list }} + when: disconnected | default(true) | bool + +- name: Migrate foundation plugin operator subscriptions + ansible.builtin.include_tasks: + file: migrations/operator_catalog_source.yaml + vars: + plugin: "{{ plugin_operator.0 }}" + operator: "{{ plugin_operator.1 }}" + catalog_mirror: "{{ mirror_certified_rh_operator_catalog if (plugin.catalog | default('redhat')) == 'certified' else mirror_rh_operator_catalog }}" + operator_name: "{{ operator.name }}" + operator_namespace: "{{ operator.namespace }}" + legacy_source: "cs-{{ catalog_mirror }}-v4-20" + new_source: "cs-{{ catalog_mirror }}-{{ plugin.name }}-v4-20" + loop: "{{ _foundation_plugins | default([]) | subelements('operators', skip_missing=True) }}" + loop_control: + loop_var: plugin_operator + label: "{{ plugin_operator.0.name }}/{{ plugin_operator.1.name }}" + when: + - disconnected | default(true) | bool + - operator.source is not defined From 2df5bef4a2ee3499d7c8c8e66f2bc2dbfd13a782 Mon Sep 17 00:00:00 2001 From: Maor Friedman Date: Tue, 9 Jun 2026 15:41:25 +0300 Subject: [PATCH 10/10] Move disconnected condition and fix debug message logic 1. Move disconnected condition to migrations.yaml: - Apply condition at the include level in migrations.yaml - Remove redundant disconnected checks from foundation_plugin_catalog_sources.yaml - Cleaner control flow - condition in one place 2. Fix debug message logic in operator_catalog_source.yaml: - Only show "migrated" when r_operator_patch.changed is true - Add branch for custom catalog sources (not legacy_source) - Add final catch-all branch for other cases - Prevents false positives when patch was blocked Addresses CodeRabbit review comments. Assisted-by: Claude Code --- playbooks/tasks/migrations.yaml | 1 + .../migrations/foundation_plugin_catalog_sources.yaml | 7 +------ playbooks/tasks/migrations/operator_catalog_source.yaml | 6 +++++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/playbooks/tasks/migrations.yaml b/playbooks/tasks/migrations.yaml index ff2b8770..3b7eaa63 100644 --- a/playbooks/tasks/migrations.yaml +++ b/playbooks/tasks/migrations.yaml @@ -10,3 +10,4 @@ - name: Foundation plugin catalog source migration ansible.builtin.include_tasks: file: migrations/foundation_plugin_catalog_sources.yaml + when: disconnected | default(true) | bool diff --git a/playbooks/tasks/migrations/foundation_plugin_catalog_sources.yaml b/playbooks/tasks/migrations/foundation_plugin_catalog_sources.yaml index 8bd939a5..13a4241c 100644 --- a/playbooks/tasks/migrations/foundation_plugin_catalog_sources.yaml +++ b/playbooks/tasks/migrations/foundation_plugin_catalog_sources.yaml @@ -19,7 +19,6 @@ recurse: true depth: 2 register: r_mig_find - when: disconnected | default(true) | bool - name: Read plugin descriptors ansible.builtin.slurp: @@ -28,7 +27,6 @@ register: r_mig_slurp loop_control: loop_var: plugin_path - when: disconnected | default(true) | bool - name: Filter foundation plugins ansible.builtin.set_fact: @@ -40,7 +38,6 @@ | selectattr('type', 'equalto', 'foundation') | selectattr('name', 'in', enabled_plugins | default([])) | list }} - when: disconnected | default(true) | bool - name: Migrate foundation plugin operator subscriptions ansible.builtin.include_tasks: @@ -57,6 +54,4 @@ loop_control: loop_var: plugin_operator label: "{{ plugin_operator.0.name }}/{{ plugin_operator.1.name }}" - when: - - disconnected | default(true) | bool - - operator.source is not defined + when: operator.source is not defined diff --git a/playbooks/tasks/migrations/operator_catalog_source.yaml b/playbooks/tasks/migrations/operator_catalog_source.yaml index 1af0789f..36de21f0 100644 --- a/playbooks/tasks/migrations/operator_catalog_source.yaml +++ b/playbooks/tasks/migrations/operator_catalog_source.yaml @@ -50,7 +50,11 @@ {{ operator_name }} subscription not found — no migration needed {% elif r_operator_subscription.resources[0].spec.source == new_source %} {{ operator_name }} subscription already using new catalog source — no migration needed - {% else %} + {% elif r_operator_patch is defined and r_operator_patch.changed %} {{ operator_name }} subscription migrated from {{ r_operator_subscription.resources[0].spec.source }} to {{ new_source }} + {% elif r_operator_subscription.resources[0].spec.source is defined and r_operator_subscription.resources[0].spec.source != legacy_source %} + {{ operator_name }} subscription has custom catalog source ({{ r_operator_subscription.resources[0].spec.source }}) — migration skipped + {% else %} + {{ operator_name }} subscription migration not performed — current source: {{ r_operator_subscription.resources[0].spec.source }} {% endif %}