@@ -29,8 +29,8 @@ KUADRANT_NAMESPACE="${KUADRANT_NAMESPACE:-kuadrant-system}"
2929
3030OPERATOR_TYPE=" ${OPERATOR_TYPE:- rhoai} " # rhoai or odh
3131CUSTOM_CATALOG=" ${CUSTOM_CATALOG:- } " # custom catalog image (e.g. quay.io/rhoai/rhoai-fbc-fragment:...)
32- RHOAI_VERSION=" ${RHOAI_VERSION:- 3.4 } "
33- RHOAI_CHANNEL=" ${RHOAI_CHANNEL:- stable- ${RHOAI_VERSION} } "
32+ RHOAI_VERSION=" ${RHOAI_VERSION:- } "
33+ RHOAI_CHANNEL=" ${RHOAI_CHANNEL:- } "
3434ODH_CHANNEL=" ${ODH_CHANNEL:- fast-3} "
3535GATEWAY_CLASS_NAME=" ${GATEWAY_CLASS_NAME:- openshift-default} "
3636GATEWAY_NAME=" ${GATEWAY_NAME:- openshift-ai-inference} "
@@ -337,6 +337,103 @@ EOF
337337
338338# ── 5. RHOAI / ODH operator ─────────────────────────────────────────────────
339339
340+ # Resolve RHOAI version and channel (only called when installation is needed).
341+ # Queries PackageManifest (with retry), then:
342+ # - Version: user-provided RHOAI_VERSION, or highest from stable-X.Y / beta channels
343+ # - Channel: user-provided RHOAI_CHANNEL, or stable-${RHOAI_VERSION} preferred, then beta
344+ resolve_rhoai_info () {
345+ # Short-circuit: if both version and channel are already provided, skip detection
346+ if [ -n " ${RHOAI_VERSION} " ] && [ -n " ${RHOAI_CHANNEL} " ]; then
347+ log " Using user-specified RHOAI version: ${RHOAI_VERSION} "
348+ log " Using user-specified RHOAI channel: ${RHOAI_CHANNEL} "
349+ return 0
350+ fi
351+
352+ # Fetch PackageManifest (with retry — CatalogSource READY does not guarantee
353+ # PackageManifest is synced yet, especially for custom catalogs)
354+ local manifest_json=" "
355+ local retries=0
356+ local max_retries=12 # 12 × 5s = 60s
357+ while [ -z " ${manifest_json} " ]; do
358+ if [ -n " ${CUSTOM_CATALOG} " ]; then
359+ local catalog_name=" ${OPERATOR_TYPE} -custom-catalog"
360+ manifest_json=$( kubectl get packagemanifest rhods-operator \
361+ -n openshift-marketplace -o json 2> /dev/null \
362+ | jq --arg cs " ${catalog_name} " ' select(.status.catalogSource == $cs)' ) || true
363+ else
364+ manifest_json=$( kubectl get packagemanifest rhods-operator \
365+ -n openshift-marketplace -o json 2> /dev/null) || true
366+ fi
367+ if [ -n " ${manifest_json} " ]; then
368+ break
369+ fi
370+ retries=$(( retries + 1 ))
371+ if [ " ${retries} " -ge " ${max_retries} " ]; then
372+ die " PackageManifest 'rhods-operator' not found after ${max_retries} attempts. Is the catalog source available?"
373+ fi
374+ log " PackageManifest not yet available, retrying (${retries} /${max_retries} )..."
375+ sleep 5
376+ done
377+
378+ # Resolve version
379+ if [ -n " ${RHOAI_VERSION} " ]; then
380+ log " Using user-specified RHOAI version: ${RHOAI_VERSION} "
381+ else
382+ local csvs
383+ if [ -n " ${RHOAI_CHANNEL} " ]; then
384+ step " Deriving RHOAI version from channel ${RHOAI_CHANNEL} ..."
385+ csvs=$( echo " ${manifest_json} " | jq -r --arg ch " ${RHOAI_CHANNEL} " '
386+ .status.channels[] | select(.name == $ch) | .currentCSV // empty' ) || true
387+ else
388+ step " Auto-detecting latest RHOAI version..."
389+ csvs=$( echo " ${manifest_json} " | jq -r '
390+ .status.channels[] |
391+ select((.name | test("^stable-[0-9]+\\.[0-9]+$")) or .name == "beta") |
392+ .currentCSV // empty' ) || true
393+ fi
394+
395+ RHOAI_VERSION=$( echo " ${csvs} " \
396+ | grep -oE ' ^rhods-operator\.[0-9]+\.[0-9]+' \
397+ | sed ' s/rhods-operator\.//' \
398+ | sort -t. -k1,1n -k2,2n -u | tail -1) || true
399+
400+ if [ -n " ${RHOAI_VERSION} " ]; then
401+ log " Latest RHOAI version: ${RHOAI_VERSION} "
402+ else
403+ die " No RHOAI version found for rhods-operator."
404+ fi
405+ fi
406+
407+ # Resolve channel — prefer stable-X.Y > stable-X.x > beta
408+ if [ -n " ${RHOAI_CHANNEL} " ]; then
409+ log " Using user-specified RHOAI channel: ${RHOAI_CHANNEL} "
410+ # Validate channel exists in catalog (catches typos in user-specified values)
411+ local validate_csv
412+ validate_csv=$( echo " ${manifest_json} " | jq -r --arg ch " ${RHOAI_CHANNEL} " '
413+ .status.channels[] | select(.name == $ch) | .currentCSV // empty' ) || true
414+ if [ -z " ${validate_csv} " ]; then
415+ die " Channel '${RHOAI_CHANNEL} ' not found in PackageManifest."
416+ fi
417+ else
418+ step " Auto-detecting RHOAI channel for version ${RHOAI_VERSION} ..."
419+ local major=" ${RHOAI_VERSION%% .* } "
420+ local candidates=(" stable-${RHOAI_VERSION} " " stable-${major} .x" " beta" )
421+ for candidate in " ${candidates[@]} " ; do
422+ local csv
423+ csv=$( echo " ${manifest_json} " | jq -r --arg ch " ${candidate} " '
424+ .status.channels[] | select(.name == $ch) | .currentCSV // empty' ) || true
425+ if [ -n " ${csv} " ] && [[ " ${csv} " == rhods-operator.${RHOAI_VERSION} .* ]]; then
426+ RHOAI_CHANNEL=" ${candidate} "
427+ log " Channel '${candidate} ' provides RHOAI ${RHOAI_VERSION} (CSV: ${csv} )"
428+ break
429+ fi
430+ done
431+ if [ -z " ${RHOAI_CHANNEL} " ]; then
432+ die " No channel provides RHOAI ${RHOAI_VERSION} . Checked: ${candidates[*]} "
433+ fi
434+ fi
435+ }
436+
340437create_custom_catalogsource () {
341438 local name=" $1 "
342439 local namespace=" $2 "
@@ -388,42 +485,51 @@ install_rhoai_operator() {
388485 operator_name=" rhods-operator"
389486 namespace=" redhat-ods-operator"
390487 catalog=" redhat-operators"
391- channel=" ${RHOAI_CHANNEL} "
392488 ;;
393489 odh)
394490 operator_name=" opendatahub-operator"
395491 namespace=" opendatahub"
396492 catalog=" community-operators"
397- channel=" ${ODH_CHANNEL} "
398493 ;;
399494 * )
400495 die " Unknown OPERATOR_TYPE: ${OPERATOR_TYPE} . Use rhoai or odh."
401496 ;;
402497 esac
403498
404- # Custom catalog: create CatalogSource and override catalog source name
405- if [ -n " ${CUSTOM_CATALOG} " ]; then
406- local custom_catalog_name=" ${OPERATOR_TYPE} -custom-catalog"
407- log " Using custom catalog: ${CUSTOM_CATALOG} "
408- create_custom_catalogsource " ${custom_catalog_name} " " openshift-marketplace" " ${CUSTOM_CATALOG} "
409- catalog=" ${custom_catalog_name} "
410- fi
411-
499+ # install operator
412500 if kubectl get subscription.operators.coreos.com " ${operator_name} " \
413501 -n " ${namespace} " & > /dev/null 2>&1 ; then
414502 log " ${OPERATOR_TYPE} operator already installed. Skipping."
415503 else
416- # Validate channel exists in catalog (skip for custom catalogs —
417- # PackageManifest may not be synced yet after CatalogSource creation)
418- if [ -z " ${CUSTOM_CATALOG} " ]; then
419- local available_channels
420- available_channels=$( kubectl get packagemanifest " ${operator_name} " \
421- -n openshift-marketplace -o jsonpath=' {.status.channels[*].name}' 2> /dev/null || echo " " )
422- if [ -z " ${available_channels} " ]; then
423- die " PackageManifest '${operator_name} ' not found in catalog. Is the catalog source available?"
504+ # Custom catalog: create CatalogSource first so PackageManifest is
505+ # available for version/channel auto-detection
506+ if [ -n " ${CUSTOM_CATALOG} " ]; then
507+ local custom_catalog_name=" ${OPERATOR_TYPE} -custom-catalog"
508+ log " Using custom catalog: ${CUSTOM_CATALOG} "
509+ create_custom_catalogsource " ${custom_catalog_name} " " openshift-marketplace" " ${CUSTOM_CATALOG} "
510+ catalog=" ${custom_catalog_name} "
511+ fi
512+
513+ # Resolve version and channel
514+ if [ " ${OPERATOR_TYPE} " = " rhoai" ]; then
515+ resolve_rhoai_info
516+ channel=" ${RHOAI_CHANNEL} "
517+ else
518+ channel=" ${ODH_CHANNEL} "
519+ # Validate ODH channel exists before creating Subscription
520+ local pm_json
521+ pm_json=$( kubectl get packagemanifest " ${operator_name} " \
522+ -n openshift-marketplace -o json 2> /dev/null) || true
523+ if [ -n " ${CUSTOM_CATALOG} " ] && [ -n " ${pm_json} " ]; then
524+ pm_json=$( echo " ${pm_json} " | jq --arg cs " ${custom_catalog_name} " \
525+ ' select(.status.catalogSource == $cs)' ) || true
424526 fi
425- if ! echo " ${available_channels} " | grep -q " ${channel} " ; then
426- die " Channel '${channel} ' not found for '${operator_name} '. Available: ${available_channels} "
527+ if [ -n " ${pm_json} " ]; then
528+ local available_channels
529+ available_channels=$( echo " ${pm_json} " | jq -r ' .status.channels[].name' | tr ' \n' ' ' ) || true
530+ if [ -n " ${available_channels} " ] && ! echo " ${available_channels} " | grep -q " ${channel} " ; then
531+ die " Channel '${channel} ' not found for '${operator_name} '. Available: ${available_channels} "
532+ fi
427533 fi
428534 fi
429535
453559 wait_for_subscription " ${namespace} " " ${operator_name} "
454560 fi
455561
456- # Verify installed version matches expected version
457- if [ " ${OPERATOR_TYPE} " = " rhoai" ]; then
562+ # Verify installed version matches expected version (skipped when already installed and no version provided)
563+ if [ " ${OPERATOR_TYPE} " = " rhoai" ] && [ -n " ${RHOAI_VERSION} " ] ; then
458564 local installed_csv
459565 installed_csv=$( kubectl get subscription.operators.coreos.com " ${operator_name} " \
460566 -n " ${namespace} " -o jsonpath=' {.status.installedCSV}' 2> /dev/null || echo " " )
466572 fi
467573 log " RHOAI installedCSV: ${installed_csv} "
468574 fi
469-
470- # Verify operator was installed from the expected catalog source
471- if [ -n " ${CUSTOM_CATALOG} " ]; then
472- local expected_catalog=" ${OPERATOR_TYPE} -custom-catalog"
473- local install_plan
474- install_plan=$( kubectl get subscription.operators.coreos.com " ${operator_name} " \
475- -n " ${namespace} " -o jsonpath=' {.status.installPlanRef.name}' 2> /dev/null || echo " " )
476- if [ -z " ${install_plan} " ]; then
477- die " No InstallPlan found for subscription '${operator_name} '."
478- fi
479- local actual_catalog
480- actual_catalog=$( kubectl get installplan " ${install_plan} " \
481- -n " ${namespace} " -o jsonpath=' {.status.bundleLookups[0].catalogSourceRef.name}' 2> /dev/null || echo " " )
482- if [ " ${actual_catalog} " != " ${expected_catalog} " ]; then
483- die " Operator installed from catalog '${actual_catalog} ' instead of '${expected_catalog} '. Custom catalog was not used!"
484- fi
485- log " Verified: operator installed from custom catalog '${actual_catalog} '."
486- fi
487575}
488576
489577apply_dsci_and_dsc () {
@@ -909,6 +997,9 @@ deploy_batch_gateway_rhoai() {
909997 --set " processor.config.modelGateways.${model_key} .initialBackoff=${GW_INITIAL_BACKOFF} "
910998 --set " processor.config.modelGateways.${model_key} .maxBackoff=${GW_MAX_BACKOFF} "
911999 --set " apiserver.config.batchAPI.passThroughHeaders={Authorization}"
1000+ # CI clusters have limited CPU; lower requests so the processor pod can be scheduled
1001+ --set " processor.resources.requests.cpu=100m"
1002+ --set " processor.resources.requests.memory=128Mi"
9121003 )
9131004
9141005 do_deploy_batch_gateway " ${helm_args[@]} "
@@ -1182,8 +1273,8 @@ usage() {
11821273 echo " Environment Variables:"
11831274 echo " OPERATOR_TYPE rhoai or odh (default: rhoai)"
11841275 echo " CUSTOM_CATALOG Custom catalog image for operator (creates CatalogSource)"
1185- echo " RHOAI_VERSION RHOAI version (default: 3.4) "
1186- echo " RHOAI_CHANNEL OLM channel (default: stable- \$ {RHOAI_VERSION}) "
1276+ echo " RHOAI_VERSION RHOAI version"
1277+ echo " RHOAI_CHANNEL RHOAI OLM channel"
11871278 echo " MODEL_NAME Model name for simulator (default: facebook/opt-125m)"
11881279 echo " MODEL_REPLICAS Number of replicas (default: 1)"
11891280 echo " SIM_IMAGE Simulator image (default: ghcr.io/llm-d/llm-d-inference-sim:v0.7.1)"
0 commit comments