Skip to content
Merged
13 changes: 13 additions & 0 deletions playbooks/tasks/migrations.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# 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: Foundation plugin catalog source migration
ansible.builtin.include_tasks:
file: migrations/foundation_plugin_catalog_sources.yaml
when: disconnected | default(true) | bool
57 changes: 57 additions & 0 deletions playbooks/tasks/migrations/foundation_plugin_catalog_sources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
# 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).
Comment on lines +7 to +10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So this migration is to be applied if we're migrating from 0.1.0, right? It shouldn't be run at any other point although it looks idempotent, right?

#
# 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

- 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

- 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 }}

- 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: operator.source is not defined
60 changes: 60 additions & 0 deletions playbooks/tasks/migrations/operator_catalog_source.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
# 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
{% 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 %}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
23 changes: 23 additions & 0 deletions playbooks/upgrade.yaml
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions upgrade.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
set -euo pipefail

# Upgrade script for Red Hat Sovereign Enclave
# Run sync.sh BEFORE running this script to ensure content is synchronized

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It would be great to have something that checks this...


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}"
}
Comment on lines +9 to +17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: can we avoid duplicating these from bootstrap.sh?


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
Loading