From ec13353f443d1b5f9c4873a916b94355472cd743 Mon Sep 17 00:00:00 2001 From: Aleksandr Kurlov Date: Thu, 2 Jul 2026 13:58:16 +0200 Subject: [PATCH] Add ACS smoke test --- .github/workflows/acs-smoke-test.yml | 139 ++++++++++++++++ smoke-test/lib.sh | 230 +++++++++++++++++++++++++++ smoke-test/upgrade-latest-test.sh | 60 +++++++ smoke-test/upgrade-oldest-test.sh | 53 ++++++ 4 files changed, 482 insertions(+) create mode 100644 .github/workflows/acs-smoke-test.yml create mode 100755 smoke-test/lib.sh create mode 100755 smoke-test/upgrade-latest-test.sh create mode 100755 smoke-test/upgrade-oldest-test.sh diff --git a/.github/workflows/acs-smoke-test.yml b/.github/workflows/acs-smoke-test.yml new file mode 100644 index 00000000..4bb51f8b --- /dev/null +++ b/.github/workflows/acs-smoke-test.yml @@ -0,0 +1,139 @@ +name: "ACS Smoke Test" + +on: + 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 + +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: ocp-default + name: ${{ env.CLUSTER_NAME }} + lifespan: ${{ inputs.cluster-lifespan }} + args: nodes=3,machine-type=e2-standard-8 + 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: Authenticate to GCP + uses: google-github-actions/auth@v3 + with: + credentials_json: ${{ secrets.GCP_RELEASE_AUTOMATION_SA }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v3 + + - name: Download cluster kubeconfig + run: | + infractl artifacts \ + --cluster-name=${{ needs.create-cluster.outputs.cluster-name }} \ + --download-dir=./artifacts + + - name: Set KUBECONFIG + run: echo "KUBECONFIG=${{ github.workspace }}/artifacts/kubeconfig" >> "$GITHUB_ENV" + + - name: "Test 1: upgrade oldest-supported → provided" + working-directory: ./smoke-test + env: + OPERATOR_INDEX_IMAGE: ${{ inputs.operator-index-image }} + ACS_VERSION: ${{ inputs.acs-version }} + 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: + OPERATOR_INDEX_IMAGE: ${{ inputs.operator-index-image }} + ACS_VERSION: ${{ inputs.acs-version }} + 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 --cluster-name=${{ 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/smoke-test/lib.sh b/smoke-test/lib.sh new file mode 100755 index 00000000..fda048d7 --- /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; return 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 60s" +} + +# 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" + 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})..." + apply_custom_catalog "$index_image" + disable_default_sources + 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" + 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..97a8b861 --- /dev/null +++ b/smoke-test/upgrade-latest-test.sh @@ -0,0 +1,60 @@ +#!/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. +LATEST_MINOR="" +wait_for_catalog "redhat-operators" "openshift-marketplace" 180 +if 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 "======================================================================"