diff --git a/.github/workflows/acs-smoke-test.yml b/.github/workflows/acs-smoke-test.yml new file mode 100644 index 00000000..3872b100 --- /dev/null +++ b/.github/workflows/acs-smoke-test.yml @@ -0,0 +1,152 @@ +name: "ACS Smoke Test" + +on: + # TODO: remove pull_request trigger before merging + pull_request: + branches: [master] + workflow_dispatch: + inputs: + operator-index-image: + description: 'ACS Operator index image (e.g., quay.io/rhacs-eng/stackrox-operator-index:ocp-v4-22-...)' + required: true + type: string + acs-version: + description: 'ACS minor version under test (e.g. 4.10). Determines channel and Y-2 oldest.' + required: true + type: string + cluster-lifespan: + description: 'Cluster lifespan (e.g., 2h, 4h, 8h)' + required: false + default: '2h' + type: string + workflow_call: + inputs: + operator-index-image: + description: 'ACS Operator index image' + required: true + type: string + acs-version: + description: 'ACS minor version under test (e.g. 4.10). Determines channel and Y-2 oldest.' + required: true + type: string + cluster-lifespan: + description: 'Cluster lifespan (e.g., 2h, 4h, 8h)' + required: false + default: '2h' + type: string + +env: + CLUSTER_NAME: smoke-${{ github.run_number }} + GH_TOKEN: ${{ github.token }} + GH_NO_UPDATE_NOTIFIER: 1 + +run-name: >- + ${{ format('ACS Smoke Test - {0}', inputs.operator-index-image) }} + +jobs: + create-cluster: + name: Create OpenShift cluster + runs-on: ubuntu-latest + outputs: + cluster-name: ${{ steps.info.outputs.name }} + steps: + - name: Set cluster name + id: info + run: echo "name=${{ env.CLUSTER_NAME }}" >> "$GITHUB_OUTPUT" + + - name: Create OpenShift cluster + uses: stackrox/actions/infra/create-cluster@v1 + with: + token: ${{ secrets.INFRA_TOKEN }} + flavor: openshift-4 + name: ${{ env.CLUSTER_NAME }} + lifespan: ${{ inputs.cluster-lifespan || '2h' }} + wait: true + + run-smoke-tests: + name: Run ACS smoke tests + needs: create-cluster + runs-on: ubuntu-latest + container: + image: quay.io/stackrox-io/apollo-ci:stackrox-test-0.5.11@sha256:df3459cf038e73775314b3a5d36d14dda41e6df438a0273c9efa8d23e0f2358a + steps: + - name: Check out code + uses: actions/checkout@v7 + + - name: Install infractl + uses: stackrox/actions/infra/install-infractl@v1 + + - name: Download cluster kubeconfig + run: | + infractl artifacts \ + --download-dir=./artifacts \ + ${{ needs.create-cluster.outputs.cluster-name }} + + - name: Set KUBECONFIG + run: echo "KUBECONFIG=${{ github.workspace }}/artifacts/kubeconfig" >> "$GITHUB_ENV" + + - name: "Test 1: upgrade oldest-supported → provided" + working-directory: ./smoke-test + env: + # Add a || operator-index image and version for testing in the PR. + # TODO: remove before merging! + OPERATOR_INDEX_IMAGE: ${{ inputs.operator-index-image || 'quay.io/rhacs-eng/stackrox-operator-index:ocp-v4-22-7a9fcba929e5858cca91a84c3e5da8c829a3c990-fast' }} + ACS_VERSION: ${{ inputs.acs-version || '4.11' }} + run: bash upgrade-oldest-test.sh + + - name: Reset operator state between tests + working-directory: ./smoke-test + run: | + source lib.sh + reset_operator + shell: bash + + - name: "Test 2: install provided → optionally upgrade to latest GA" + working-directory: ./smoke-test + env: + # Add a || operator-index image and version for testing in the PR. + # TODO: remove before merging! + OPERATOR_INDEX_IMAGE: ${{ inputs.operator-index-image || 'quay.io/rhacs-eng/stackrox-operator-index:ocp-v4-22-7a9fcba929e5858cca91a84c3e5da8c829a3c990-fast' }} + ACS_VERSION: ${{ inputs.acs-version || '4.11' }} + run: bash upgrade-latest-test.sh + + cleanup-cluster: + name: Cleanup cluster + needs: [create-cluster, run-smoke-tests] + runs-on: ubuntu-latest + if: always() && needs.create-cluster.result != 'cancelled' + steps: + - name: Install infractl + uses: stackrox/actions/infra/install-infractl@v1 + + - name: Authenticate to GCP + uses: google-github-actions/auth@v3 + with: + credentials_json: ${{ secrets.GCP_RELEASE_AUTOMATION_SA }} + + - name: Delete cluster + run: infractl delete ${{ needs.create-cluster.outputs.cluster-name }} + + report-status: + name: Report test status + needs: [create-cluster, run-smoke-tests, cleanup-cluster] + runs-on: ubuntu-latest + if: always() + steps: + - name: Write summary + run: | + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + # ACS Smoke Test Results + + | Field | Value | + |-------|-------| + | **Operator image** | `${{ inputs.operator-index-image }}` | + | **Cluster** | `${{ needs.create-cluster.outputs.cluster-name }}` | + | **Create cluster** | ${{ needs.create-cluster.result }} | + | **Smoke tests** | ${{ needs.run-smoke-tests.result }} | + | **Cleanup** | ${{ needs.cleanup-cluster.result }} | + + ## Tests run: + 1. **Test 1** — install oldest supported ACS version, upgrade to the provided operator-index image + 2. **Test 2** — install provided operator-index image, upgrade to latest GA if minor is behind + EOF diff --git a/.github/workflows/auto-smoke-test.yml b/.github/workflows/auto-smoke-test.yml new file mode 100644 index 00000000..3712cab9 --- /dev/null +++ b/.github/workflows/auto-smoke-test.yml @@ -0,0 +1,142 @@ +name: "Auto Smoke Test" + +on: + workflow_dispatch: + inputs: + acs-version-override: + description: 'Override ACS version, skips bundles.yaml diff (e.g. "4.10")' + required: false + type: string + operator-index-image-override: + description: 'Override operator-index image, skips Quay polling (e.g. quay.io/rhacs-eng/stackrox-operator-index:ocp-v4-22-...)' + required: false + type: string + push: + branches: [master] + paths: ['bundles.yaml'] + pull_request: + paths: ['bundles.yaml'] + +env: + # OCP version used to construct the Konflux-built operator-index image tag. + # Update when infra cluster uses a newer OCP version. + OCP_VERSION: v4-22 + +jobs: + derive-inputs: + name: Derive smoke test inputs + runs-on: ubuntu-latest + outputs: + acs-version: ${{ steps.version.outputs.acs-version }} + operator-index-image: ${{ steps.image.outputs.operator-index-image }} + should-run: ${{ steps.version.outputs.should-run }} + steps: + - name: Check out code + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Determine ACS version + id: version + env: + ACS_VERSION_OVERRIDE: ${{ inputs.acs-version-override }} + OPERATOR_INDEX_IMAGE_OVERRIDE: ${{ inputs.operator-index-image-override }} + BASE_REF: ${{ github.base_ref }} + EVENT_NAME: ${{ github.event_name }} + BEFORE_SHA: ${{ github.event.before }} + run: | + # Image override without version override is ambiguous — fail fast. + if [ -n "$OPERATOR_INDEX_IMAGE_OVERRIDE" ] && [ -z "$ACS_VERSION_OVERRIDE" ]; then + echo "::error::operator-index-image-override requires acs-version-override to also be set" + exit 1 + fi + + if [ -n "$ACS_VERSION_OVERRIDE" ]; then + echo "acs-version=$ACS_VERSION_OVERRIDE" >> "$GITHUB_OUTPUT" + echo "should-run=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "$EVENT_NAME" = "pull_request" ]; then + DIFF_BASE="origin/$BASE_REF" + else + # Use event.before to capture all commits in the push, not just the tip commit. + DIFF_BASE="$BEFORE_SHA" + # event.before is all-zeros on first push to a branch; fall back to parent. + if [[ "$DIFF_BASE" =~ ^0+$ ]]; then + DIFF_BASE="HEAD~1" + fi + fi + + # Exclude diff headers first, then match only the `version:` key (not + # `oldest_supported_version:` or other keys that contain "version"). + NEW_VERSIONS=$(git diff "$DIFF_BASE" -- bundles.yaml \ + | grep -v '^+++' \ + | grep '^+[[:space:]]*version:' \ + | sed 's/.*version:[[:space:]]*//' \ + | sort -V || true) + + if [ -z "$NEW_VERSIONS" ]; then + echo "No new versions found in bundles.yaml diff, skipping smoke test" + echo "should-run=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + LATEST=$(echo "$NEW_VERSIONS" | tail -1) + MINOR=$(echo "$LATEST" | cut -d. -f1-2) + echo "New versions added: $(echo "$NEW_VERSIONS" | tr '\n' ' '), testing minor: $MINOR" + echo "acs-version=$MINOR" >> "$GITHUB_OUTPUT" + echo "should-run=true" >> "$GITHUB_OUTPUT" + + - name: Determine operator-index image + id: image + if: steps.version.outputs.should-run == 'true' + env: + OPERATOR_INDEX_IMAGE_OVERRIDE: ${{ inputs.operator-index-image-override }} + run: | + if [ -n "$OPERATOR_INDEX_IMAGE_OVERRIDE" ]; then + echo "operator-index-image=$OPERATOR_INDEX_IMAGE_OVERRIDE" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "${{ github.event_name }}" = "pull_request" ]; then + SHA="${{ github.event.pull_request.head.sha }}" + else + SHA="${{ github.sha }}" + fi + + TAG="ocp-${OCP_VERSION}-${SHA}-fast" + IMAGE="quay.io/rhacs-eng/stackrox-operator-index:${TAG}" + echo "Waiting for Konflux to build: $IMAGE" + + for i in $(seq 1 40); do + if RESPONSE=$(curl -sf --max-time 30 --connect-timeout 10 \ + "https://quay.io/api/v1/repository/rhacs-eng/stackrox-operator-index/tag/?specificTag=${TAG}"); then + FOUND=$(echo "$RESPONSE" | python3 -c \ + "import json,sys; d=json.load(sys.stdin); print('true' if d.get('tags') else 'false')" \ + 2>/dev/null || echo 'false') + if [ "$FOUND" = "true" ]; then + echo "Image available after attempt $i: $IMAGE" + echo "operator-index-image=$IMAGE" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "Attempt $i/40: image not yet in Quay, retrying in 3 minutes..." + else + echo "Attempt $i/40: Quay API request failed (network or HTTP error), retrying in 3 minutes..." + fi + [ "$i" -lt 40 ] && sleep 180 + done + + echo "Timed out after 2 hours waiting for: $IMAGE" + exit 1 + + smoke-test: + name: Run smoke test + needs: derive-inputs + if: needs.derive-inputs.outputs.should-run == 'true' + uses: ./.github/workflows/acs-smoke-test.yml + with: + operator-index-image: ${{ needs.derive-inputs.outputs.operator-index-image }} + acs-version: ${{ needs.derive-inputs.outputs.acs-version }} + cluster-lifespan: 3h + secrets: inherit diff --git a/smoke-test/lib.sh b/smoke-test/lib.sh new file mode 100755 index 00000000..03bf234f --- /dev/null +++ b/smoke-test/lib.sh @@ -0,0 +1,230 @@ +#!/usr/bin/env bash +# Shared functions for ACS operator smoke tests. + +set -euo pipefail + +# Logging helpers +step() { echo; echo "══════════════════════════════════════════════════════"; echo " $*"; echo "══════════════════════════════════════════════════════"; } +info() { echo " $*"; } +ok() { echo " ✅ $*"; } +warn() { echo " ⚠️ $*"; } +fail() { echo " ❌ $*" >&2; exit 1; } + +# Splits "4.10" → ACS_MAJOR=4, ACS_MINOR=10 +parse_acs_version() { + local ver="$1" + ACS_MAJOR=$(echo "$ver" | cut -d. -f1) + ACS_MINOR=$(echo "$ver" | cut -d. -f2) + [[ "$ACS_MAJOR" =~ ^[0-9]+$ && "$ACS_MINOR" =~ ^[0-9]+$ ]] \ + || fail "ACS_VERSION must be MAJOR.MINOR (e.g. 4.10), got: ${ver}" +} + +# Prints the highest minor version in rhacs-MAJOR.N channels from the official catalog. +get_latest_official_minor() { + local major="${1:-4}" + oc get packagemanifest -n openshift-marketplace \ + -l catalog=redhat-operators \ + -o jsonpath='{range .items[?(@.metadata.name=="rhacs-operator")].status.channels[*]}{.name}{"\n"}{end}' \ + | grep -oE "rhacs-${major}\.[0-9]+" \ + | grep -oE '[0-9]+$' \ + | sort -n \ + | tail -1 \ + || true +} + +disable_default_sources() { + info "Disabling default OperatorHub sources..." + oc patch OperatorHub cluster --type json \ + -p '[{"op":"add","path":"/spec/disableAllDefaultSources","value":true}]' + ok "Default sources disabled" +} + +enable_default_sources() { + info "Enabling default OperatorHub sources..." + oc patch OperatorHub cluster --type json \ + -p '[{"op":"add","path":"/spec/disableAllDefaultSources","value":false}]' + ok "Default sources enabled" +} + +apply_custom_catalog() { + local index_image="$1" + info "Applying custom CatalogSource (image: ${index_image})..." + oc apply -f - </dev/null || true) + if [[ "$state" == "READY" ]]; then + ok "CatalogSource/${name} is READY" + return 0 + fi + info " state=${state:-pending}, retrying in 10s..." + sleep 10 + done + fail "CatalogSource/${name} not READY within ${timeout}s" +} + +apply_subscription() { + local channel="$1" source="$2" source_ns="${3:-openshift-marketplace}" + info "Applying Subscription (channel=${channel}, source=${source})..." + oc apply -f - </dev/null || true) + if [[ -n "$TARGET_CSV" ]]; then + info "Target CSV: ${TARGET_CSV}" + return 0 + fi + info " packagemanifest not ready yet, retrying in 10s..." + sleep 10 + done + fail "Could not resolve currentCSV for channel ${channel} from ${catalog_label} after ${timeout}s" +} + +# Install from official redhat-operators at channel rhacs-MAJOR.MINOR. +# Sets TARGET_CSV to the channel's currentCSV. +install_from_official() { + local major="$1" minor="$2" + local channel="rhacs-${major}.${minor}" + info "Installing ACS Operator from redhat-operators, channel ${channel}..." + wait_for_catalog "redhat-operators" "openshift-marketplace" 120 + _resolve_target_csv "redhat-operators" "$channel" 120 + apply_subscription "$channel" "redhat-operators" +} + +# Install from the custom index image on the given channel (disables default sources). +# Sets TARGET_CSV to the channel's currentCSV. +install_from_custom() { + local index_image="$1" channel="$2" + info "Installing ACS Operator from custom index, channel ${channel}..." + disable_default_sources + apply_custom_catalog "$index_image" + wait_for_catalog "my-operator-catalog" + _resolve_target_csv "my-operator-catalog" "$channel" + apply_subscription "$channel" "my-operator-catalog" +} + +# Upgrade by refreshing the custom CatalogSource and pointing the subscription at it. +# Sets TARGET_CSV to the channel's currentCSV. +upgrade_via_custom() { + local index_image="$1" channel="$2" + info "Upgrading via custom catalog (channel: ${channel})..." + disable_default_sources + apply_custom_catalog "$index_image" + wait_for_catalog "my-operator-catalog" + _resolve_target_csv "my-operator-catalog" "$channel" + apply_subscription "$channel" "my-operator-catalog" + ok "Subscription updated — OLM will upgrade to ${TARGET_CSV}" +} + +# Upgrade to latest official GA via redhat-operators. +# Sets TARGET_CSV to the latest channel's currentCSV. +upgrade_to_latest_official() { + local major="${1:-4}" + enable_default_sources + wait_for_catalog "redhat-operators" "openshift-marketplace" 180 + local latest_minor + latest_minor=$(get_latest_official_minor "$major") || true + [[ -n "$latest_minor" ]] || fail "Could not determine latest GA minor from redhat-operators" + local channel="rhacs-${major}.${latest_minor}" + info "Upgrading to latest GA channel: ${channel}..." + _resolve_target_csv "redhat-operators" "$channel" 180 + apply_subscription "$channel" "redhat-operators" + ok "Subscription updated to ${channel}, target: ${TARGET_CSV}" +} + +get_current_csv() { + oc get csv -n openshift-operators --no-headers 2>/dev/null \ + | awk '/rhacs-operator/ && /Succeeded/ {print $1}' \ + | head -1 +} + +# Waits for a specific CSV to reach Succeeded phase. +# Use after install/upgrade to wait for the exact target version, not just any change. +# This correctly handles multi-hop OLM upgrade graphs (e.g. 4.8→4.9→4.10). +wait_for_csv() { + local target_csv="$1" timeout="${2:-600}" + local deadline + deadline=$(( $(date +%s) + timeout )) + info "Waiting for ${target_csv} to reach Succeeded (timeout: ${timeout}s)..." + while (( $(date +%s) < deadline )); do + local phase + phase=$(oc get csv "$target_csv" -n openshift-operators \ + -o jsonpath='{.status.phase}' 2>/dev/null || true) + if [[ "$phase" == "Succeeded" ]]; then + ok "${target_csv} is Succeeded" + return 0 + fi + # Show in-progress CSVs so logs show the hop chain + local progress + progress=$(oc get csv -n openshift-operators --no-headers 2>/dev/null \ + | awk '/rhacs-operator/ {printf "%s(%s) ", $1, $7}' || true) + info " ${progress:+CSVs: ${progress}}phase=${phase:-not found}, waiting 15s..." + sleep 15 + done + fail "${target_csv} did not reach Succeeded within ${timeout}s" +} + +log_csv() { + local csv + csv=$(oc get csv -n openshift-operators --no-headers 2>/dev/null \ + | grep rhacs-operator || echo "") + info "Current CSV: ${csv}" +} + +# Reset between tests +reset_operator() { + info "Resetting operator state for next test..." + oc delete subscription rhacs-operator -n openshift-operators --ignore-not-found + oc get csv -n openshift-operators --no-headers 2>/dev/null \ + | awk '/rhacs-operator/ {print $1}' \ + | xargs -r oc delete csv -n openshift-operators --ignore-not-found || true + oc delete catalogsource my-operator-catalog -n openshift-marketplace --ignore-not-found || true + enable_default_sources + ok "Operator state reset" +} diff --git a/smoke-test/upgrade-latest-test.sh b/smoke-test/upgrade-latest-test.sh new file mode 100755 index 00000000..18a756dc --- /dev/null +++ b/smoke-test/upgrade-latest-test.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Test 2 — Install provided version, optionally upgrade to latest GA +# +# 1. Installs ACS Operator from OPERATOR_INDEX_IMAGE on the ACS_VERSION channel +# 2. If ACS_VERSION minor < latest GA minor: upgrades to latest GA via redhat-operators +# 3. Verifies CSV reaches Succeeded after each step +# +# Required env vars: +# OPERATOR_INDEX_IMAGE custom operator index image +# ACS_VERSION ACS minor version to test, e.g. "4.10" + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib.sh +source "${SCRIPT_DIR}/lib.sh" + +OPERATOR_INDEX_IMAGE="${OPERATOR_INDEX_IMAGE:-${MY_INDEX_IMAGE:-}}" +ACS_VERSION="${ACS_VERSION:-}" + +[[ -n "$OPERATOR_INDEX_IMAGE" ]] || fail "OPERATOR_INDEX_IMAGE is required" +[[ -n "$ACS_VERSION" ]] || fail "ACS_VERSION is required (e.g. 4.10)" + +parse_acs_version "$ACS_VERSION" +CHANNEL="rhacs-${ACS_MAJOR}.${ACS_MINOR}" + +echo "======================================================================" +echo " TEST 2: Install provided → optionally upgrade to latest GA" +echo " Image: ${OPERATOR_INDEX_IMAGE}" +echo " Version: ${ACS_MAJOR}.${ACS_MINOR} | Channel: ${CHANNEL}" +echo "======================================================================" + +# Query latest GA minor NOW while redhat-operators is still enabled. +# install_from_custom() disables default sources, making this unavailable later. +# Use || true so a catalog timeout degrades gracefully to skipping the upgrade check. +LATEST_MINOR="" +if wait_for_catalog "redhat-operators" "openshift-marketplace" 180 && + latest=$(get_latest_official_minor "$ACS_MAJOR") && [[ -n "$latest" ]]; then + LATEST_MINOR="$latest" + info "Latest GA in redhat-operators: ${ACS_MAJOR}.${LATEST_MINOR}" +else + warn "Could not determine latest GA minor — upgrade check will be skipped" +fi + +step "Step 1: Install ACS Operator ${CHANNEL} from custom index" +install_from_custom "$OPERATOR_INDEX_IMAGE" "$CHANNEL" +wait_for_csv "$TARGET_CSV" 600 +log_csv + +if [[ -n "$LATEST_MINOR" ]] && (( ACS_MINOR < LATEST_MINOR )); then + step "Step 2: Upgrade ${ACS_MAJOR}.${ACS_MINOR} → ${ACS_MAJOR}.${LATEST_MINOR} (latest GA)" + upgrade_to_latest_official "$ACS_MAJOR" + wait_for_csv "$TARGET_CSV" 600 + log_csv +elif [[ -n "$LATEST_MINOR" ]]; then + info "${ACS_MAJOR}.${ACS_MINOR} is already at latest GA minor (${LATEST_MINOR}) — no upgrade needed" +fi + +echo +echo "======================================================================" +echo " ✅ TEST 2 PASSED" +echo "======================================================================" diff --git a/smoke-test/upgrade-oldest-test.sh b/smoke-test/upgrade-oldest-test.sh new file mode 100755 index 00000000..e587234b --- /dev/null +++ b/smoke-test/upgrade-oldest-test.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Test 1 — Upgrade from oldest supported to provided version +# +# 1. Installs ACS Operator (oldest_supported_version from bundles.yaml) from official redhat-operators +# 2. Upgrades to the provided OPERATOR_INDEX_IMAGE on the ACS_VERSION channel +# 3. Verifies CSV reaches Succeeded after each step +# +# Required env vars: +# OPERATOR_INDEX_IMAGE custom operator index image +# ACS_VERSION ACS minor version to test, e.g. "4.10" + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib.sh +source "${SCRIPT_DIR}/lib.sh" + +OPERATOR_INDEX_IMAGE="${OPERATOR_INDEX_IMAGE:-${MY_INDEX_IMAGE:-}}" +ACS_VERSION="${ACS_VERSION:-}" + +[[ -n "$OPERATOR_INDEX_IMAGE" ]] || fail "OPERATOR_INDEX_IMAGE is required" +[[ -n "$ACS_VERSION" ]] || fail "ACS_VERSION is required (e.g. 4.10)" + +parse_acs_version "$ACS_VERSION" +CHANNEL="rhacs-${ACS_MAJOR}.${ACS_MINOR}" + +BUNDLES_YAML="${SCRIPT_DIR}/../bundles.yaml" +[[ -f "$BUNDLES_YAML" ]] || fail "bundles.yaml not found at ${BUNDLES_YAML}" +OLDEST_VERSION=$(awk '/^oldest_supported_version:/{print $2; exit}' "$BUNDLES_YAML") +[[ -n "$OLDEST_VERSION" ]] || fail "oldest_supported_version not found in bundles.yaml" +OLDEST_MAJOR=$(echo "$OLDEST_VERSION" | cut -d. -f1) +OLDEST_MINOR=$(echo "$OLDEST_VERSION" | cut -d. -f2) + +echo "======================================================================" +echo " TEST 1: Upgrade oldest-supported → provided" +echo " Image: ${OPERATOR_INDEX_IMAGE}" +echo " Version: ${ACS_MAJOR}.${ACS_MINOR} | Channel: ${CHANNEL}" +echo " Oldest: ${OLDEST_MAJOR}.${OLDEST_MINOR} (from bundles.yaml)" +echo "======================================================================" + +step "Step 1: Install ACS Operator ${OLDEST_MAJOR}.${OLDEST_MINOR} from redhat-operators" +install_from_official "$OLDEST_MAJOR" "$OLDEST_MINOR" +wait_for_csv "$TARGET_CSV" 300 +log_csv + +step "Step 2: Upgrade to ${CHANNEL} via custom index" +upgrade_via_custom "$OPERATOR_INDEX_IMAGE" "$CHANNEL" +wait_for_csv "$TARGET_CSV" 1200 +log_csv + +echo +echo "======================================================================" +echo " ✅ TEST 1 PASSED" +echo "======================================================================"