From 9fc4cc193a12ad90e8b415edd0bfa1732e81b5ae Mon Sep 17 00:00:00 2001 From: Laurent TOURREAU Date: Sun, 10 May 2026 02:15:55 +0200 Subject: [PATCH 1/5] Add cleanup-on-delete component for RHOAI instances (2.x and 3.x) Implements an ArgoCD PreDelete hook that runs the official Red Hat RHOAI uninstall procedure automatically before ArgoCD removes Application resources. Without this, ArgoCD deletions leave namespaces stuck in Terminating and orphaned webhooks that block future reinstalls. The component is enabled by default in all instance overlays since the cleanup problem affects every user who deletes the RHOAI Application. Resources created (all PreDelete hooks, zero runtime footprint): - ServiceAccount/rhoai-cleanup in openshift-gitops - ClusterRole/rhoai-cleanup - ClusterRoleBinding/rhoai-cleanup - Job/delete-rhoai-resources in openshift-gitops The Job runs in openshift-gitops to survive while RHOAI namespaces are being deleted, and executes a 10-step cleanup procedure per Red Hat documentation: DSC deletion, operator Subscription/CSV removal, pod termination wait, OperatorGroup removal, webhook cleanup, namespace deletion, and CRD removal with finalizer fallback. Validated end-to-end on OCP 4.20 with RHOAI 3.3.1 (fast-3.x channel). Co-Authored-By: Claude Sonnet 4.6 --- .../components/cleanup-on-delete/README.md | 47 +++ .../delete-rhoai-resources.yaml | 324 ++++++++++++++++++ .../cleanup-on-delete/kustomization.yaml | 5 + .../kustomization.yaml | 1 + .../eus-2.16-nvidia-gpu/kustomization.yaml | 1 + .../overlays/eus-2.16/kustomization.yaml | 1 + .../eus-2.25-nvidia-gpu/kustomization.yaml | 1 + .../overlays/eus-2.25/kustomization.yaml | 1 + .../fast-nvidia-gpu/kustomization.yaml | 1 + .../overlays/fast/kustomization.yaml | 1 + .../components/cleanup-on-delete/README.md | 47 +++ .../delete-rhoai-resources.yaml | 324 ++++++++++++++++++ .../cleanup-on-delete/kustomization.yaml | 5 + .../stable-3.3-nvidia-gpu/kustomization.yaml | 1 + 14 files changed, 760 insertions(+) create mode 100644 components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/README.md create mode 100644 components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.yaml create mode 100644 components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/kustomization.yaml create mode 100644 components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/README.md create mode 100644 components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.yaml create mode 100644 components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/kustomization.yaml diff --git a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/README.md b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/README.md new file mode 100644 index 000000000..2bbb1c1a5 --- /dev/null +++ b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/README.md @@ -0,0 +1,47 @@ +# cleanup-on-delete + +Runs the [official Red Hat RHOAI uninstall procedure](https://docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed) automatically as an ArgoCD `PreDelete` hook when the RHOAI Application is deleted. + +Without this component, ArgoCD deletes RHOAI manifests in an arbitrary order, which leaves namespaces stuck in `Terminating` and webhooks that block future reinstalls. + +## What it does + +Executes a 10-step cleanup Job before ArgoCD removes any resources: + +1. Delete the `DataScienceCluster` CR (triggers operator cleanup) +2. Wait 60 seconds for the operator to finish cleanup +3. Delete the operator `Subscription` +4. Delete the operator `ClusterServiceVersion` +5. Wait for operator pods to terminate +6. Delete the `OperatorGroup` +7. Delete all RHOAI `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration` resources +8. Delete RHOAI namespaces (`redhat-ods-operator`, `redhat-ods-applications`, `redhat-ods-monitoring`) +9. Delete RHOAI CRDs (`opendatahub.io`, `kubeflow.org`) +10. Verify cleanup and report status + +## How to enable + +Add this component to your instance overlay's `components:` list: + +```yaml +# components/operators/openshift-ai/instance-2.x/overlays/stable-2.25-nvidia-gpu/kustomization.yaml +components: + - ../../components/cleanup-on-delete + - ../../components/components-kserve + # ... other components +``` + +## Resources created + +All resources are ArgoCD `PreDelete` hooks and are automatically cleaned up after the Job completes: + +- `ServiceAccount/rhoai-cleanup` in `openshift-gitops` +- `ClusterRole/rhoai-cleanup` +- `ClusterRoleBinding/rhoai-cleanup` +- `Job/delete-rhoai-resources` in `openshift-gitops` + +## Notes + +- `backoffLimit: 0` — the Job does not retry; partial cleanup is fine since remaining resources are deleted by ArgoCD +- The Job exits with code `0` even when warnings occur, so ArgoCD always proceeds with Application deletion +- The Job runs in `openshift-gitops` (not `redhat-ods-operator`) to avoid being killed when RHOAI namespaces are deleted diff --git a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.yaml b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.yaml new file mode 100644 index 000000000..ed060d500 --- /dev/null +++ b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.yaml @@ -0,0 +1,324 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/sync-wave: "-1" + name: rhoai-cleanup + namespace: openshift-gitops +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/sync-wave: "-1" + name: rhoai-cleanup +rules: + - apiGroups: + - "" + resources: + - namespaces + - pods + verbs: + - delete + - get + - list + - patch + - watch + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + - validatingwebhookconfigurations + verbs: + - delete + - get + - list + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - delete + - get + - list + - patch + - apiGroups: + - opendatahub.io + resources: + - datascienceclusters + - dscinitializations + verbs: + - delete + - get + - list + - patch + - apiGroups: + - operators.coreos.com + resources: + - clusterserviceversions + - operatorgroups + - subscriptions + verbs: + - delete + - get + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/sync-wave: "-1" + name: rhoai-cleanup +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: rhoai-cleanup +subjects: + - kind: ServiceAccount + name: rhoai-cleanup + namespace: openshift-gitops +--- +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/hook-delete-policy: HookSucceeded,HookFailed + argocd.argoproj.io/sync-options: Force=true + argocd.argoproj.io/sync-wave: "0" + name: delete-rhoai-resources + namespace: openshift-gitops +spec: + template: + metadata: + name: delete-rhoai-resources + spec: + containers: + - command: + - /bin/bash + - -c + - | + set -e + + echo "================================================" + echo "Red Hat OpenShift AI Cleanup - Official Procedure" + echo "================================================" + echo "" + echo "Following Red Hat documentation:" + echo "docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed" + echo "" + echo "This PreDelete hook runs BEFORE ArgoCD deletes Application resources," + echo "ensuring proper RHOAI deletion order per Red Hat documentation." + echo "" + + # Step 1: Delete the DataScienceCluster CR + # Per Red Hat docs: "Delete the DataScienceCluster instance" + echo "Step 1: Deleting DataScienceCluster CR..." + if oc get datasciencecluster.opendatahub.io default-dsc &>/dev/null; then + oc delete datasciencecluster.opendatahub.io default-dsc --timeout=120s --ignore-not-found 2>&1 || { + echo " ⚠️ Deletion timed out, removing finalizers..." + oc patch datasciencecluster.opendatahub.io default-dsc --type=json \ + -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true + oc delete datasciencecluster.opendatahub.io default-dsc \ + --ignore-not-found --grace-period=0 --force 2>/dev/null || true + } + echo " ✅ DataScienceCluster deleted" + else + echo " ℹ️ DataScienceCluster not found" + fi + echo "" + + # Step 2: Wait for operator cleanup + echo "Step 2: Waiting for RHOAI operator cleanup (60s grace period)..." + sleep 60 + echo " ✅ Grace period completed" + echo "" + + # Step 3: Delete RHOAI operator Subscription + # Per Red Hat docs: "Delete the Subscription object" + echo "Step 3: Deleting RHOAI operator Subscription..." + if oc get subscription.operators.coreos.com rhods-operator -n redhat-ods-operator &>/dev/null; then + oc delete subscription.operators.coreos.com rhods-operator \ + -n redhat-ods-operator --ignore-not-found --timeout=30s 2>&1 || true + echo " ✅ Subscription deleted" + else + echo " ℹ️ Subscription not found" + fi + echo "" + + # Step 4: Delete ClusterServiceVersion + # Per Red Hat docs: "Delete the ClusterServiceVersion object" + echo "Step 4: Deleting RHOAI CSV..." + CSV_NAME=$(oc get csv.operators.coreos.com -n redhat-ods-operator \ + -o name 2>/dev/null | grep rhods-operator || echo "") + if [ -n "$CSV_NAME" ]; then + oc delete "$CSV_NAME" -n redhat-ods-operator --ignore-not-found --timeout=60s 2>&1 || true + echo " ✅ CSV deleted: $CSV_NAME" + else + echo " ℹ️ CSV not found" + fi + echo "" + + # Step 5: Wait for operator pods to terminate + echo "Step 5: Waiting for RHOAI operator pods to terminate..." + if oc get namespace redhat-ods-operator &>/dev/null; then + oc wait --for=delete pod --all -n redhat-ods-operator --timeout=120s 2>/dev/null || { + echo " ⚠️ Timeout waiting for pods, force deleting..." + oc delete pod --all -n redhat-ods-operator --grace-period=0 --force 2>/dev/null || true + } + echo " ✅ Operator pods terminated" + else + echo " ℹ️ No operator pods found" + fi + echo "" + + # Step 6: Delete OperatorGroup + echo "Step 6: Deleting OperatorGroup..." + if oc get operatorgroup.operators.coreos.com rhods-operator -n redhat-ods-operator &>/dev/null; then + oc delete operatorgroup.operators.coreos.com rhods-operator \ + -n redhat-ods-operator --ignore-not-found --timeout=30s 2>&1 || true + echo " ✅ OperatorGroup deleted" + else + echo " ℹ️ OperatorGroup not found" + fi + echo "" + + # Step 7: Delete webhooks (prevents namespace termination deadlock) + echo "Step 7: Deleting RHOAI webhooks..." + + VALIDATING_WEBHOOKS=$(oc get validatingwebhookconfiguration -o name 2>/dev/null \ + | grep -E "opendatahub|kserve|rhoai" || echo "") + if [ -n "$VALIDATING_WEBHOOKS" ]; then + for webhook in $VALIDATING_WEBHOOKS; do + oc delete "$webhook" --ignore-not-found 2>&1 || true + echo " ✅ Deleted: $webhook" + done + else + echo " ℹ️ No validating webhooks found" + fi + + MUTATING_WEBHOOKS=$(oc get mutatingwebhookconfiguration -o name 2>/dev/null \ + | grep -E "opendatahub|kserve|rhoai" || echo "") + if [ -n "$MUTATING_WEBHOOKS" ]; then + for webhook in $MUTATING_WEBHOOKS; do + oc delete "$webhook" --ignore-not-found 2>&1 || true + echo " ✅ Deleted: $webhook" + done + else + echo " ℹ️ No mutating webhooks found" + fi + echo "" + + # Step 8: Delete RHOAI namespaces + echo "Step 8: Deleting RHOAI namespaces..." + + NAMESPACES=( + "redhat-ods-applications" + "redhat-ods-monitoring" + "redhat-ods-operator" + ) + + for ns in "${NAMESPACES[@]}"; do + if oc get namespace "$ns" &>/dev/null; then + echo " Deleting namespace: $ns" + oc delete namespace "$ns" --timeout=120s 2>&1 || { + echo " ⚠️ Timeout, removing finalizers..." + oc patch namespace "$ns" --type=json \ + -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true + oc patch namespace "$ns" --type=json \ + -p='[{"op": "remove", "path": "/spec/finalizers"}]' 2>/dev/null || true + oc delete namespace "$ns" --grace-period=0 --force 2>/dev/null || true + } + for i in {1..30}; do + if ! oc get namespace "$ns" &>/dev/null; then + echo " ✅ Namespace $ns deleted" + break + fi + if [ "$i" -eq 30 ]; then + echo " ⚠️ Namespace $ns still exists after cleanup attempts" + fi + sleep 2 + done + else + echo " ℹ️ Namespace $ns not found" + fi + done + echo "" + + # Step 9: Delete RHOAI CRDs + # Per Red Hat docs: "Delete the CRDs" + echo "Step 9: Deleting RHOAI CRDs..." + + safe_delete_crd() { + local crd_name=$1 + if oc get crd "$crd_name" &>/dev/null; then + echo " Deleting CRD: $crd_name" + oc delete crd "$crd_name" --timeout=60s --ignore-not-found 2>&1 || { + echo " ⚠️ Timeout, removing finalizers..." + oc patch crd "$crd_name" --type=json \ + -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true + oc delete crd "$crd_name" --ignore-not-found --grace-period=0 --force 2>/dev/null || true + } + echo " ✅ CRD $crd_name deleted" + fi + } + + RHOAI_CRDS=$(oc get crd -o name 2>/dev/null \ + | grep -E "opendatahub.io|kubeflow.org" || echo "") + if [ -n "$RHOAI_CRDS" ]; then + for crd in $RHOAI_CRDS; do + crd_name=$(echo "$crd" | sed 's|customresourcedefinition.apiextensions.k8s.io/||') + safe_delete_crd "$crd_name" + done + else + echo " ℹ️ No RHOAI CRDs found" + fi + echo "" + + # Step 10: Verification + echo "Step 10: Verifying cleanup..." + CLEANUP_SUCCESS=true + + for ns in "${NAMESPACES[@]}"; do + if oc get namespace "$ns" &>/dev/null; then + echo " ⚠️ Namespace $ns still exists" + CLEANUP_SUCCESS=false + fi + done + + if [ "$CLEANUP_SUCCESS" = true ]; then + echo " ✅ All namespaces removed" + fi + + REMAINING_WEBHOOKS=$(oc get validatingwebhookconfiguration,mutatingwebhookconfiguration \ + -o name 2>/dev/null | grep -E "opendatahub|kserve|rhoai" || echo "") + if [ -n "$REMAINING_WEBHOOKS" ]; then + echo " ⚠️ Some webhooks still exist:" + echo "$REMAINING_WEBHOOKS" | sed 's/^/ /' + CLEANUP_SUCCESS=false + else + echo " ✅ All webhooks removed" + fi + + echo "" + + if [ "$CLEANUP_SUCCESS" = true ]; then + echo "================================================" + echo "✅ RHOAI cleanup completed successfully" + echo "================================================" + else + echo "================================================" + echo "⚠️ RHOAI cleanup completed with warnings" + echo " Manual verification recommended" + echo "================================================" + exit 0 # Warnings are non-fatal; ArgoCD continues deletion + fi + image: registry.redhat.io/openshift4/ose-cli:latest + name: delete-rhoai-resources + restartPolicy: Never + serviceAccountName: rhoai-cleanup + backoffLimit: 0 diff --git a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/kustomization.yaml b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/kustomization.yaml new file mode 100644 index 000000000..efb6e649b --- /dev/null +++ b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +resources: + - delete-rhoai-resources.yaml diff --git a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16-modelregistry-only/kustomization.yaml b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16-modelregistry-only/kustomization.yaml index 15d4350e3..ae59a52b1 100644 --- a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16-modelregistry-only/kustomization.yaml +++ b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16-modelregistry-only/kustomization.yaml @@ -5,6 +5,7 @@ resources: - ../../base components: + - ../../components/cleanup-on-delete - ../../components/components-modelregistry - ../../components/rhoai-dashboard-access - ../../components/wait-for-servicemesh diff --git a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16-nvidia-gpu/kustomization.yaml b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16-nvidia-gpu/kustomization.yaml index 08d0e8fba..9fc8ccdd5 100644 --- a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16-nvidia-gpu/kustomization.yaml +++ b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16-nvidia-gpu/kustomization.yaml @@ -9,6 +9,7 @@ resources: components: - ../../components/anythingllm-image - ../../components/auth-with-authorino + - ../../components/cleanup-on-delete - ../../components/components-kserve - ../../components/components-modelmesh - ../../components/components-training diff --git a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16/kustomization.yaml b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16/kustomization.yaml index ab5fe1576..a5ab87098 100644 --- a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16/kustomization.yaml +++ b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.16/kustomization.yaml @@ -8,6 +8,7 @@ resources: components: - ../../components/auth-with-authorino + - ../../components/cleanup-on-delete - ../../components/components-kserve - ../../components/components-modelmesh - ../../components/components-training diff --git a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.25-nvidia-gpu/kustomization.yaml b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.25-nvidia-gpu/kustomization.yaml index 300d75ecb..35a2419a2 100644 --- a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.25-nvidia-gpu/kustomization.yaml +++ b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.25-nvidia-gpu/kustomization.yaml @@ -6,6 +6,7 @@ resources: components: - ../../components/anythingllm-image + - ../../components/cleanup-on-delete - ../../components/components-kserve-rawdeployments-only - ../../components/components-llamastack - ../../components/components-modelregistry diff --git a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.25/kustomization.yaml b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.25/kustomization.yaml index d4993c8b8..b0f299307 100644 --- a/components/operators/openshift-ai/instance-2.x/overlays/eus-2.25/kustomization.yaml +++ b/components/operators/openshift-ai/instance-2.x/overlays/eus-2.25/kustomization.yaml @@ -5,6 +5,7 @@ resources: - ../../base components: + - ../../components/cleanup-on-delete - ../../components/components-kserve-rawdeployments-only - ../../components/components-llamastack - ../../components/components-modelregistry diff --git a/components/operators/openshift-ai/instance-2.x/overlays/fast-nvidia-gpu/kustomization.yaml b/components/operators/openshift-ai/instance-2.x/overlays/fast-nvidia-gpu/kustomization.yaml index 300d75ecb..35a2419a2 100644 --- a/components/operators/openshift-ai/instance-2.x/overlays/fast-nvidia-gpu/kustomization.yaml +++ b/components/operators/openshift-ai/instance-2.x/overlays/fast-nvidia-gpu/kustomization.yaml @@ -6,6 +6,7 @@ resources: components: - ../../components/anythingllm-image + - ../../components/cleanup-on-delete - ../../components/components-kserve-rawdeployments-only - ../../components/components-llamastack - ../../components/components-modelregistry diff --git a/components/operators/openshift-ai/instance-2.x/overlays/fast/kustomization.yaml b/components/operators/openshift-ai/instance-2.x/overlays/fast/kustomization.yaml index d4993c8b8..b0f299307 100644 --- a/components/operators/openshift-ai/instance-2.x/overlays/fast/kustomization.yaml +++ b/components/operators/openshift-ai/instance-2.x/overlays/fast/kustomization.yaml @@ -5,6 +5,7 @@ resources: - ../../base components: + - ../../components/cleanup-on-delete - ../../components/components-kserve-rawdeployments-only - ../../components/components-llamastack - ../../components/components-modelregistry diff --git a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/README.md b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/README.md new file mode 100644 index 000000000..2bbb1c1a5 --- /dev/null +++ b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/README.md @@ -0,0 +1,47 @@ +# cleanup-on-delete + +Runs the [official Red Hat RHOAI uninstall procedure](https://docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed) automatically as an ArgoCD `PreDelete` hook when the RHOAI Application is deleted. + +Without this component, ArgoCD deletes RHOAI manifests in an arbitrary order, which leaves namespaces stuck in `Terminating` and webhooks that block future reinstalls. + +## What it does + +Executes a 10-step cleanup Job before ArgoCD removes any resources: + +1. Delete the `DataScienceCluster` CR (triggers operator cleanup) +2. Wait 60 seconds for the operator to finish cleanup +3. Delete the operator `Subscription` +4. Delete the operator `ClusterServiceVersion` +5. Wait for operator pods to terminate +6. Delete the `OperatorGroup` +7. Delete all RHOAI `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration` resources +8. Delete RHOAI namespaces (`redhat-ods-operator`, `redhat-ods-applications`, `redhat-ods-monitoring`) +9. Delete RHOAI CRDs (`opendatahub.io`, `kubeflow.org`) +10. Verify cleanup and report status + +## How to enable + +Add this component to your instance overlay's `components:` list: + +```yaml +# components/operators/openshift-ai/instance-2.x/overlays/stable-2.25-nvidia-gpu/kustomization.yaml +components: + - ../../components/cleanup-on-delete + - ../../components/components-kserve + # ... other components +``` + +## Resources created + +All resources are ArgoCD `PreDelete` hooks and are automatically cleaned up after the Job completes: + +- `ServiceAccount/rhoai-cleanup` in `openshift-gitops` +- `ClusterRole/rhoai-cleanup` +- `ClusterRoleBinding/rhoai-cleanup` +- `Job/delete-rhoai-resources` in `openshift-gitops` + +## Notes + +- `backoffLimit: 0` — the Job does not retry; partial cleanup is fine since remaining resources are deleted by ArgoCD +- The Job exits with code `0` even when warnings occur, so ArgoCD always proceeds with Application deletion +- The Job runs in `openshift-gitops` (not `redhat-ods-operator`) to avoid being killed when RHOAI namespaces are deleted diff --git a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.yaml b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.yaml new file mode 100644 index 000000000..ed060d500 --- /dev/null +++ b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.yaml @@ -0,0 +1,324 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/sync-wave: "-1" + name: rhoai-cleanup + namespace: openshift-gitops +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/sync-wave: "-1" + name: rhoai-cleanup +rules: + - apiGroups: + - "" + resources: + - namespaces + - pods + verbs: + - delete + - get + - list + - patch + - watch + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + - validatingwebhookconfigurations + verbs: + - delete + - get + - list + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - delete + - get + - list + - patch + - apiGroups: + - opendatahub.io + resources: + - datascienceclusters + - dscinitializations + verbs: + - delete + - get + - list + - patch + - apiGroups: + - operators.coreos.com + resources: + - clusterserviceversions + - operatorgroups + - subscriptions + verbs: + - delete + - get + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/sync-wave: "-1" + name: rhoai-cleanup +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: rhoai-cleanup +subjects: + - kind: ServiceAccount + name: rhoai-cleanup + namespace: openshift-gitops +--- +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/hook-delete-policy: HookSucceeded,HookFailed + argocd.argoproj.io/sync-options: Force=true + argocd.argoproj.io/sync-wave: "0" + name: delete-rhoai-resources + namespace: openshift-gitops +spec: + template: + metadata: + name: delete-rhoai-resources + spec: + containers: + - command: + - /bin/bash + - -c + - | + set -e + + echo "================================================" + echo "Red Hat OpenShift AI Cleanup - Official Procedure" + echo "================================================" + echo "" + echo "Following Red Hat documentation:" + echo "docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed" + echo "" + echo "This PreDelete hook runs BEFORE ArgoCD deletes Application resources," + echo "ensuring proper RHOAI deletion order per Red Hat documentation." + echo "" + + # Step 1: Delete the DataScienceCluster CR + # Per Red Hat docs: "Delete the DataScienceCluster instance" + echo "Step 1: Deleting DataScienceCluster CR..." + if oc get datasciencecluster.opendatahub.io default-dsc &>/dev/null; then + oc delete datasciencecluster.opendatahub.io default-dsc --timeout=120s --ignore-not-found 2>&1 || { + echo " ⚠️ Deletion timed out, removing finalizers..." + oc patch datasciencecluster.opendatahub.io default-dsc --type=json \ + -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true + oc delete datasciencecluster.opendatahub.io default-dsc \ + --ignore-not-found --grace-period=0 --force 2>/dev/null || true + } + echo " ✅ DataScienceCluster deleted" + else + echo " ℹ️ DataScienceCluster not found" + fi + echo "" + + # Step 2: Wait for operator cleanup + echo "Step 2: Waiting for RHOAI operator cleanup (60s grace period)..." + sleep 60 + echo " ✅ Grace period completed" + echo "" + + # Step 3: Delete RHOAI operator Subscription + # Per Red Hat docs: "Delete the Subscription object" + echo "Step 3: Deleting RHOAI operator Subscription..." + if oc get subscription.operators.coreos.com rhods-operator -n redhat-ods-operator &>/dev/null; then + oc delete subscription.operators.coreos.com rhods-operator \ + -n redhat-ods-operator --ignore-not-found --timeout=30s 2>&1 || true + echo " ✅ Subscription deleted" + else + echo " ℹ️ Subscription not found" + fi + echo "" + + # Step 4: Delete ClusterServiceVersion + # Per Red Hat docs: "Delete the ClusterServiceVersion object" + echo "Step 4: Deleting RHOAI CSV..." + CSV_NAME=$(oc get csv.operators.coreos.com -n redhat-ods-operator \ + -o name 2>/dev/null | grep rhods-operator || echo "") + if [ -n "$CSV_NAME" ]; then + oc delete "$CSV_NAME" -n redhat-ods-operator --ignore-not-found --timeout=60s 2>&1 || true + echo " ✅ CSV deleted: $CSV_NAME" + else + echo " ℹ️ CSV not found" + fi + echo "" + + # Step 5: Wait for operator pods to terminate + echo "Step 5: Waiting for RHOAI operator pods to terminate..." + if oc get namespace redhat-ods-operator &>/dev/null; then + oc wait --for=delete pod --all -n redhat-ods-operator --timeout=120s 2>/dev/null || { + echo " ⚠️ Timeout waiting for pods, force deleting..." + oc delete pod --all -n redhat-ods-operator --grace-period=0 --force 2>/dev/null || true + } + echo " ✅ Operator pods terminated" + else + echo " ℹ️ No operator pods found" + fi + echo "" + + # Step 6: Delete OperatorGroup + echo "Step 6: Deleting OperatorGroup..." + if oc get operatorgroup.operators.coreos.com rhods-operator -n redhat-ods-operator &>/dev/null; then + oc delete operatorgroup.operators.coreos.com rhods-operator \ + -n redhat-ods-operator --ignore-not-found --timeout=30s 2>&1 || true + echo " ✅ OperatorGroup deleted" + else + echo " ℹ️ OperatorGroup not found" + fi + echo "" + + # Step 7: Delete webhooks (prevents namespace termination deadlock) + echo "Step 7: Deleting RHOAI webhooks..." + + VALIDATING_WEBHOOKS=$(oc get validatingwebhookconfiguration -o name 2>/dev/null \ + | grep -E "opendatahub|kserve|rhoai" || echo "") + if [ -n "$VALIDATING_WEBHOOKS" ]; then + for webhook in $VALIDATING_WEBHOOKS; do + oc delete "$webhook" --ignore-not-found 2>&1 || true + echo " ✅ Deleted: $webhook" + done + else + echo " ℹ️ No validating webhooks found" + fi + + MUTATING_WEBHOOKS=$(oc get mutatingwebhookconfiguration -o name 2>/dev/null \ + | grep -E "opendatahub|kserve|rhoai" || echo "") + if [ -n "$MUTATING_WEBHOOKS" ]; then + for webhook in $MUTATING_WEBHOOKS; do + oc delete "$webhook" --ignore-not-found 2>&1 || true + echo " ✅ Deleted: $webhook" + done + else + echo " ℹ️ No mutating webhooks found" + fi + echo "" + + # Step 8: Delete RHOAI namespaces + echo "Step 8: Deleting RHOAI namespaces..." + + NAMESPACES=( + "redhat-ods-applications" + "redhat-ods-monitoring" + "redhat-ods-operator" + ) + + for ns in "${NAMESPACES[@]}"; do + if oc get namespace "$ns" &>/dev/null; then + echo " Deleting namespace: $ns" + oc delete namespace "$ns" --timeout=120s 2>&1 || { + echo " ⚠️ Timeout, removing finalizers..." + oc patch namespace "$ns" --type=json \ + -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true + oc patch namespace "$ns" --type=json \ + -p='[{"op": "remove", "path": "/spec/finalizers"}]' 2>/dev/null || true + oc delete namespace "$ns" --grace-period=0 --force 2>/dev/null || true + } + for i in {1..30}; do + if ! oc get namespace "$ns" &>/dev/null; then + echo " ✅ Namespace $ns deleted" + break + fi + if [ "$i" -eq 30 ]; then + echo " ⚠️ Namespace $ns still exists after cleanup attempts" + fi + sleep 2 + done + else + echo " ℹ️ Namespace $ns not found" + fi + done + echo "" + + # Step 9: Delete RHOAI CRDs + # Per Red Hat docs: "Delete the CRDs" + echo "Step 9: Deleting RHOAI CRDs..." + + safe_delete_crd() { + local crd_name=$1 + if oc get crd "$crd_name" &>/dev/null; then + echo " Deleting CRD: $crd_name" + oc delete crd "$crd_name" --timeout=60s --ignore-not-found 2>&1 || { + echo " ⚠️ Timeout, removing finalizers..." + oc patch crd "$crd_name" --type=json \ + -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true + oc delete crd "$crd_name" --ignore-not-found --grace-period=0 --force 2>/dev/null || true + } + echo " ✅ CRD $crd_name deleted" + fi + } + + RHOAI_CRDS=$(oc get crd -o name 2>/dev/null \ + | grep -E "opendatahub.io|kubeflow.org" || echo "") + if [ -n "$RHOAI_CRDS" ]; then + for crd in $RHOAI_CRDS; do + crd_name=$(echo "$crd" | sed 's|customresourcedefinition.apiextensions.k8s.io/||') + safe_delete_crd "$crd_name" + done + else + echo " ℹ️ No RHOAI CRDs found" + fi + echo "" + + # Step 10: Verification + echo "Step 10: Verifying cleanup..." + CLEANUP_SUCCESS=true + + for ns in "${NAMESPACES[@]}"; do + if oc get namespace "$ns" &>/dev/null; then + echo " ⚠️ Namespace $ns still exists" + CLEANUP_SUCCESS=false + fi + done + + if [ "$CLEANUP_SUCCESS" = true ]; then + echo " ✅ All namespaces removed" + fi + + REMAINING_WEBHOOKS=$(oc get validatingwebhookconfiguration,mutatingwebhookconfiguration \ + -o name 2>/dev/null | grep -E "opendatahub|kserve|rhoai" || echo "") + if [ -n "$REMAINING_WEBHOOKS" ]; then + echo " ⚠️ Some webhooks still exist:" + echo "$REMAINING_WEBHOOKS" | sed 's/^/ /' + CLEANUP_SUCCESS=false + else + echo " ✅ All webhooks removed" + fi + + echo "" + + if [ "$CLEANUP_SUCCESS" = true ]; then + echo "================================================" + echo "✅ RHOAI cleanup completed successfully" + echo "================================================" + else + echo "================================================" + echo "⚠️ RHOAI cleanup completed with warnings" + echo " Manual verification recommended" + echo "================================================" + exit 0 # Warnings are non-fatal; ArgoCD continues deletion + fi + image: registry.redhat.io/openshift4/ose-cli:latest + name: delete-rhoai-resources + restartPolicy: Never + serviceAccountName: rhoai-cleanup + backoffLimit: 0 diff --git a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/kustomization.yaml b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/kustomization.yaml new file mode 100644 index 000000000..efb6e649b --- /dev/null +++ b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +resources: + - delete-rhoai-resources.yaml diff --git a/components/operators/openshift-ai/instance-3.x/overlays/stable-3.3-nvidia-gpu/kustomization.yaml b/components/operators/openshift-ai/instance-3.x/overlays/stable-3.3-nvidia-gpu/kustomization.yaml index 9d9a5f82f..eaa22c84b 100644 --- a/components/operators/openshift-ai/instance-3.x/overlays/stable-3.3-nvidia-gpu/kustomization.yaml +++ b/components/operators/openshift-ai/instance-3.x/overlays/stable-3.3-nvidia-gpu/kustomization.yaml @@ -5,6 +5,7 @@ resources: - ../../base components: + - ../../components/cleanup-on-delete - ../../components/components-aipipelines - ../../components/components-feast - ../../components/components-kserve From 852bbe74e196478dd3a7415714d8307ad2b25c1f Mon Sep 17 00:00:00 2001 From: Laurent TOURREAU Date: Sun, 10 May 2026 02:25:14 +0200 Subject: [PATCH 2/5] Add reinstalls and webhooks to spell check wordlist Co-Authored-By: Claude Sonnet 4.6 --- .wordlist-txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.wordlist-txt b/.wordlist-txt index 93ec0d537..1081fcf4b 100644 --- a/.wordlist-txt +++ b/.wordlist-txt @@ -332,6 +332,7 @@ rawdeployments rbac rebase redhat +reinstalls redistributions repo repo's @@ -384,6 +385,7 @@ vSphere vllm vscode vsphere +webhooks wordlist workspaces xlarge From 67f84400079454706e8fe6ce2b0cff2ef52ab4bb Mon Sep 17 00:00:00 2001 From: Laurent TOURREAU Date: Tue, 12 May 2026 22:10:15 +0200 Subject: [PATCH 3/5] Rework cleanup-on-delete per reviewer feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move bash script to separate delete-rhoai-resources.sh file mounted via ConfigMap, following the aws-gpu-machineset component pattern - Delete user workload CRs first (InferenceServices, Notebooks, Ray, Kueue, TrustyAI, etc.) to prevent finalizer deadlocks — root cause of the brute-force approach in the previous version - Replace dynamic grep-based CRD deletion with explicit CRD list per Red Hat documentation (rhoai-2.25-uninstall.md) - Remove force deletion, finalizer patching, arbitrary sleep, and pod wait — all unnecessary when deletion order is correct - Fix namespace deletion: use oc delete namespace instead of oc delete project (ClusterRole grants namespaces, not projects resource) - Add missing CRDs: nemoguardrails.trustyai.opendatahub.io and trainers.components.platform.opendatahub.io - Expand ClusterRole to cover all workload CR API groups Validated end-to-end on OCP 4.20 with RHOAI 3.3.1 (fast-3.x): all namespaces, CRDs, and webhooks removed cleanly in 55s. Co-Authored-By: Claude Sonnet 4.6 --- .../components/cleanup-on-delete/README.md | 27 +- .../delete-rhoai-resources.sh | 303 ++++++++++++++++++ .../delete-rhoai-resources.yaml | 275 +++------------- .../cleanup-on-delete/kustomization.yaml | 21 ++ .../components/cleanup-on-delete/README.md | 27 +- .../delete-rhoai-resources.sh | 303 ++++++++++++++++++ .../delete-rhoai-resources.yaml | 275 +++------------- .../cleanup-on-delete/kustomization.yaml | 21 ++ 8 files changed, 764 insertions(+), 488 deletions(-) create mode 100644 components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.sh create mode 100644 components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.sh diff --git a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/README.md b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/README.md index 2bbb1c1a5..c2a2dd96b 100644 --- a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/README.md +++ b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/README.md @@ -6,25 +6,20 @@ Without this component, ArgoCD deletes RHOAI manifests in an arbitrary order, wh ## What it does -Executes a 10-step cleanup Job before ArgoCD removes any resources: - -1. Delete the `DataScienceCluster` CR (triggers operator cleanup) -2. Wait 60 seconds for the operator to finish cleanup -3. Delete the operator `Subscription` -4. Delete the operator `ClusterServiceVersion` -5. Wait for operator pods to terminate -6. Delete the `OperatorGroup` -7. Delete all RHOAI `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration` resources -8. Delete RHOAI namespaces (`redhat-ods-operator`, `redhat-ods-applications`, `redhat-ods-monitoring`) -9. Delete RHOAI CRDs (`opendatahub.io`, `kubeflow.org`) -10. Verify cleanup and report status +Executes a 5-step cleanup Job before ArgoCD removes any resources: + +1. Delete all user workload CRs across all namespaces (InferenceServices, Notebooks, RayJobs, etc.) — prevents finalizer deadlocks +2. Delete the `DataScienceCluster` and `DSCInitialization` CRs +3. Delete OLM resources: `Subscription`, `ClusterServiceVersion`, `OperatorGroup`, `InstallPlan` +4. Delete RHOAI `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration` resources +5. Delete RHOAI namespaces and CRDs (explicit list per Red Hat documentation) ## How to enable Add this component to your instance overlay's `components:` list: ```yaml -# components/operators/openshift-ai/instance-2.x/overlays/stable-2.25-nvidia-gpu/kustomization.yaml +# components/operators/openshift-ai/instance-2.x/overlays/stable-2.25/kustomization.yaml components: - ../../components/cleanup-on-delete - ../../components/components-kserve @@ -35,6 +30,7 @@ components: All resources are ArgoCD `PreDelete` hooks and are automatically cleaned up after the Job completes: +- `ConfigMap/rhoai-cleanup-script` in `openshift-gitops` — mounts the cleanup script - `ServiceAccount/rhoai-cleanup` in `openshift-gitops` - `ClusterRole/rhoai-cleanup` - `ClusterRoleBinding/rhoai-cleanup` @@ -42,6 +38,7 @@ All resources are ArgoCD `PreDelete` hooks and are automatically cleaned up afte ## Notes -- `backoffLimit: 0` — the Job does not retry; partial cleanup is fine since remaining resources are deleted by ArgoCD -- The Job exits with code `0` even when warnings occur, so ArgoCD always proceeds with Application deletion +- The cleanup script is maintained in `delete-rhoai-resources.sh` and mounted into the Job via a ConfigMap +- `backoffLimit: 0` — the Job does not retry - The Job runs in `openshift-gitops` (not `redhat-ods-operator`) to avoid being killed when RHOAI namespaces are deleted +- The Job exits with code `0` even when resources are not found, so ArgoCD always proceeds with Application deletion diff --git a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.sh b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.sh new file mode 100644 index 000000000..8eb695bc5 --- /dev/null +++ b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.sh @@ -0,0 +1,303 @@ +#!/usr/bin/env bash + +set -e + +echo "================================================" +echo "Red Hat OpenShift AI Cleanup" +echo "================================================" +echo "" +echo "Following: docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed" +echo "" + +# Step 0: Delete user workload CRs across all namespaces before removing the DSC. +# Deleting CRs first prevents finalizer deadlocks during operator cleanup. +echo "Step 0: Deleting user workload CRs..." + +# Data Science Pipelines +oc delete datasciencepipelinesapplications.datasciencepipelinesapplications.opendatahub.io \ + -A --all --ignore-not-found 2>/dev/null || true +oc delete datasciencepipelines.components.platform.opendatahub.io \ + -A --all --ignore-not-found 2>/dev/null || true + +# Argo Workflows +oc delete clusterworkflowtemplates.argoproj.io --all --ignore-not-found 2>/dev/null || true +oc delete cronworkflows.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflowartifactgctasks.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workfloweventbindings.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflows.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflowtaskresults.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflowtasksets.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflowtemplates.argoproj.io -A --all --ignore-not-found 2>/dev/null || true + +# KServe +oc delete clusterstoragecontainers.serving.kserve.io --all --ignore-not-found 2>/dev/null || true +oc delete inferencegraphs.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete inferenceservices.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete llminferenceserviceconfigs.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete llminferenceservices.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete predictors.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete servingruntimes.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete trainedmodels.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true + +# Inference (networking) +oc delete inferencemodels.inference.networking.x-k8s.io -A --all --ignore-not-found 2>/dev/null || true +oc delete inferencepools.inference.networking.x-k8s.io -A --all --ignore-not-found 2>/dev/null || true + +# Kueue +oc delete admissionchecks.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete clusterqueues.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete cohorts.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete localqueues.kueue.x-k8s.io -A --all --ignore-not-found 2>/dev/null || true +oc delete multikueueclusters.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete multikueueconfigs.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete provisioningrequestconfigs.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete resourceflavors.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete topologies.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete workloadpriorityclasses.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete workloads.kueue.x-k8s.io -A --all --ignore-not-found 2>/dev/null || true + +# Workbenches +oc delete notebooks.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true + +# Kubeflow Training Operator +oc delete jaxjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete mpijobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete mxjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete paddlejobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete pipelines.pipelines.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete pipelineversions.pipelines.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete pytorchjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete scheduledworkflows.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete tfjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete viewers.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete xgboostjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true + +# TrustyAI +oc delete guardrailsorchestrators.trustyai.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete lmevaljobs.trustyai.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete nemoguardrails.trustyai.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete trustyaiservices.trustyai.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true + +# Ray +oc delete rayclusters.ray.io -A --all --ignore-not-found 2>/dev/null || true +oc delete rayjobs.ray.io -A --all --ignore-not-found 2>/dev/null || true +oc delete rayservices.ray.io -A --all --ignore-not-found 2>/dev/null || true + +# CodeFlare / AppWrappers +oc delete appwrappers.workload.codeflare.dev -A --all --ignore-not-found 2>/dev/null || true + +# Feast +oc delete featurestores.feast.dev -A --all --ignore-not-found 2>/dev/null || true + +# LlamaStack +oc delete llamastackdistributions.llamastack.io -A --all --ignore-not-found 2>/dev/null || true + +# Model Registry +oc delete modelregistries.modelregistry.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true + +# RHOAI / ODH platform CRs +oc delete accounts.nim.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete applications.app.k8s.io -A --all --ignore-not-found 2>/dev/null || true +oc delete auths.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete dashboards.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete feastoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete kserves.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete kueues.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete llamastackoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete modelcontrollers.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete modelmeshservings.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete modelregistries.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete monitorings.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete rays.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete servicemeshes.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete trainingoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete trustyais.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workbenches.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true + +echo " Done" +echo "" + +# Step 1: Delete DataScienceCluster and DSCInitialization +echo "Step 1: Deleting DataScienceCluster and DSCInitialization..." +oc delete datascienceclusters.datasciencecluster.opendatahub.io --all --ignore-not-found 2>/dev/null || true +oc delete dscinitializations.dscinitialization.opendatahub.io --all --ignore-not-found 2>/dev/null || true +oc delete featuretrackers.features.opendatahub.io --all --ignore-not-found 2>/dev/null || true +echo " Done" +echo "" + +# Step 2: Delete OLM resources +echo "Step 2: Deleting Subscription, CSV, OperatorGroup, InstallPlan..." +oc delete subscription --all -n redhat-ods-operator --ignore-not-found +oc delete clusterserviceversion --all -n redhat-ods-operator --ignore-not-found +oc delete operatorgroup --all -n redhat-ods-operator --ignore-not-found +oc delete installplan --all -n redhat-ods-operator --ignore-not-found +echo " Done" +echo "" + +# Step 3: Delete webhooks +echo "Step 3: Deleting RHOAI webhooks..." +oc get validatingwebhookconfiguration -o name 2>/dev/null \ + | grep -E "opendatahub|kserve|rhoai" \ + | xargs -r oc delete --ignore-not-found 2>/dev/null || true +oc get mutatingwebhookconfiguration -o name 2>/dev/null \ + | grep -E "opendatahub|kserve|rhoai" \ + | xargs -r oc delete --ignore-not-found 2>/dev/null || true +echo " Done" +echo "" + +# Step 4: Delete RHOAI namespaces +echo "Step 4: Deleting RHOAI namespaces..." +oc delete namespace redhat-ods-applications --ignore-not-found 2>/dev/null || true +oc delete namespace redhat-ods-monitoring --ignore-not-found 2>/dev/null || true +oc delete namespace redhat-ods-operator --ignore-not-found 2>/dev/null || true +oc delete namespace rhods-notebooks --ignore-not-found 2>/dev/null || true +echo " Done" +echo "" + +# Step 5: Delete RHOAI CRDs +echo "Step 5: Deleting RHOAI CRDs..." + +# Data Science Pipelines +oc delete crd \ + datasciencepipelinesapplications.datasciencepipelinesapplications.opendatahub.io \ + datasciencepipelines.components.platform.opendatahub.io \ + --ignore-not-found 2>/dev/null || true + +# Argo Workflows +oc delete crd \ + clusterworkflowtemplates.argoproj.io \ + cronworkflows.argoproj.io \ + workflowartifactgctasks.argoproj.io \ + workfloweventbindings.argoproj.io \ + workflows.argoproj.io \ + workflowtaskresults.argoproj.io \ + workflowtasksets.argoproj.io \ + workflowtemplates.argoproj.io \ + --ignore-not-found 2>/dev/null || true + +# KServe +oc delete crd \ + clusterstoragecontainers.serving.kserve.io \ + inferencegraphs.serving.kserve.io \ + inferenceservices.serving.kserve.io \ + llminferenceserviceconfigs.serving.kserve.io \ + llminferenceservices.serving.kserve.io \ + predictors.serving.kserve.io \ + servingruntimes.serving.kserve.io \ + trainedmodels.serving.kserve.io \ + --ignore-not-found 2>/dev/null || true + +# Inference (networking) +oc delete crd \ + inferencemodels.inference.networking.x-k8s.io \ + inferencepools.inference.networking.x-k8s.io \ + --ignore-not-found 2>/dev/null || true + +# Kueue +oc delete crd \ + admissionchecks.kueue.x-k8s.io \ + clusterqueues.kueue.x-k8s.io \ + cohorts.kueue.x-k8s.io \ + localqueues.kueue.x-k8s.io \ + multikueueclusters.kueue.x-k8s.io \ + multikueueconfigs.kueue.x-k8s.io \ + provisioningrequestconfigs.kueue.x-k8s.io \ + resourceflavors.kueue.x-k8s.io \ + topologies.kueue.x-k8s.io \ + workloadpriorityclasses.kueue.x-k8s.io \ + workloads.kueue.x-k8s.io \ + --ignore-not-found 2>/dev/null || true + +# Workbenches / Kubeflow +oc delete crd \ + notebooks.kubeflow.org \ + viewers.kubeflow.org \ + --ignore-not-found 2>/dev/null || true + +# Kubeflow Training Operator +oc delete crd \ + jaxjobs.kubeflow.org \ + mpijobs.kubeflow.org \ + mxjobs.kubeflow.org \ + paddlejobs.kubeflow.org \ + pipelines.pipelines.kubeflow.org \ + pipelineversions.pipelines.kubeflow.org \ + pytorchjobs.kubeflow.org \ + scheduledworkflows.kubeflow.org \ + tfjobs.kubeflow.org \ + xgboostjobs.kubeflow.org \ + --ignore-not-found 2>/dev/null || true + +# TrustyAI +oc delete crd \ + guardrailsorchestrators.trustyai.opendatahub.io \ + lmevaljobs.trustyai.opendatahub.io \ + nemoguardrails.trustyai.opendatahub.io \ + trustyaiservices.trustyai.opendatahub.io \ + --ignore-not-found 2>/dev/null || true + +# Ray +oc delete crd \ + rayclusters.ray.io \ + rayjobs.ray.io \ + rayservices.ray.io \ + --ignore-not-found 2>/dev/null || true + +# CodeFlare +oc delete crd appwrappers.workload.codeflare.dev --ignore-not-found 2>/dev/null || true + +# Feast +oc delete crd \ + feastoperators.components.platform.opendatahub.io \ + featurestores.feast.dev \ + --ignore-not-found 2>/dev/null || true + +# LlamaStack +oc delete crd llamastackdistributions.llamastack.io --ignore-not-found 2>/dev/null || true + +# RHOAI / ODH platform components +oc delete crd \ + accounts.nim.opendatahub.io \ + applications.app.k8s.io \ + auths.services.platform.opendatahub.io \ + codeflares.components.platform.opendatahub.io \ + dashboards.components.platform.opendatahub.io \ + kserves.components.platform.opendatahub.io \ + kueues.components.platform.opendatahub.io \ + llamastackoperators.components.platform.opendatahub.io \ + mlflowoperators.components.platform.opendatahub.io \ + modelcontrollers.components.platform.opendatahub.io \ + modelmeshservings.components.platform.opendatahub.io \ + modelregistries.components.platform.opendatahub.io \ + modelregistries.modelregistry.opendatahub.io \ + monitorings.services.platform.opendatahub.io \ + rays.components.platform.opendatahub.io \ + servicemeshes.services.platform.opendatahub.io \ + trainingoperators.components.platform.opendatahub.io \ + trainers.components.platform.opendatahub.io \ + trustyais.components.platform.opendatahub.io \ + workbenches.components.platform.opendatahub.io \ + --ignore-not-found 2>/dev/null || true + +# RHOAI core +oc delete crd \ + acceleratorprofiles.dashboard.opendatahub.io \ + datascienceclusters.datasciencecluster.opendatahub.io \ + dscinitializations.dscinitialization.opendatahub.io \ + featuretrackers.features.opendatahub.io \ + gatewayconfigs.services.platform.opendatahub.io \ + hardwareprofiles.dashboard.opendatahub.io \ + hardwareprofiles.infrastructure.opendatahub.io \ + modelsasservices.components.platform.opendatahub.io \ + odhapplications.dashboard.opendatahub.io \ + odhdashboardconfigs.opendatahub.io \ + odhdocuments.dashboard.opendatahub.io \ + odhquickstarts.console.openshift.io \ + --ignore-not-found 2>/dev/null || true + +echo " Done" +echo "" + +echo "================================================" +echo "RHOAI cleanup completed" +echo "================================================" diff --git a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.yaml b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.yaml index ed060d500..a533f0ce5 100644 --- a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.yaml +++ b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.yaml @@ -20,13 +20,10 @@ rules: - "" resources: - namespaces - - pods verbs: - delete - get - list - - patch - - watch - apiGroups: - admissionregistration.k8s.io resources: @@ -44,23 +41,50 @@ rules: - delete - get - list - - patch - apiGroups: + - operators.coreos.com + resources: + - clusterserviceversions + - installplans + - operatorgroups + - subscriptions + verbs: + - delete + - get + - list + - apiGroups: + - components.platform.opendatahub.io + - dashboard.opendatahub.io + - datasciencecluster.opendatahub.io + - datasciencepipelinesapplications.opendatahub.io + - dscinitialization.opendatahub.io + - features.opendatahub.io + - infrastructure.opendatahub.io + - modelregistry.opendatahub.io + - nim.opendatahub.io - opendatahub.io + - services.platform.opendatahub.io + - trustyai.opendatahub.io resources: - - datascienceclusters - - dscinitializations + - "*" verbs: - delete - get - list - - patch - apiGroups: - - operators.coreos.com + - app.k8s.io + - argoproj.io + - console.openshift.io + - feast.dev + - inference.networking.x-k8s.io + - kubeflow.org + - kueue.x-k8s.io + - llamastack.io + - ray.io + - serving.kserve.io + - workload.codeflare.dev resources: - - clusterserviceversions - - operatorgroups - - subscriptions + - "*" verbs: - delete - get @@ -93,6 +117,7 @@ metadata: name: delete-rhoai-resources namespace: openshift-gitops spec: + backoffLimit: 0 template: metadata: name: delete-rhoai-resources @@ -100,225 +125,17 @@ spec: containers: - command: - /bin/bash - - -c - - | - set -e - - echo "================================================" - echo "Red Hat OpenShift AI Cleanup - Official Procedure" - echo "================================================" - echo "" - echo "Following Red Hat documentation:" - echo "docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed" - echo "" - echo "This PreDelete hook runs BEFORE ArgoCD deletes Application resources," - echo "ensuring proper RHOAI deletion order per Red Hat documentation." - echo "" - - # Step 1: Delete the DataScienceCluster CR - # Per Red Hat docs: "Delete the DataScienceCluster instance" - echo "Step 1: Deleting DataScienceCluster CR..." - if oc get datasciencecluster.opendatahub.io default-dsc &>/dev/null; then - oc delete datasciencecluster.opendatahub.io default-dsc --timeout=120s --ignore-not-found 2>&1 || { - echo " ⚠️ Deletion timed out, removing finalizers..." - oc patch datasciencecluster.opendatahub.io default-dsc --type=json \ - -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true - oc delete datasciencecluster.opendatahub.io default-dsc \ - --ignore-not-found --grace-period=0 --force 2>/dev/null || true - } - echo " ✅ DataScienceCluster deleted" - else - echo " ℹ️ DataScienceCluster not found" - fi - echo "" - - # Step 2: Wait for operator cleanup - echo "Step 2: Waiting for RHOAI operator cleanup (60s grace period)..." - sleep 60 - echo " ✅ Grace period completed" - echo "" - - # Step 3: Delete RHOAI operator Subscription - # Per Red Hat docs: "Delete the Subscription object" - echo "Step 3: Deleting RHOAI operator Subscription..." - if oc get subscription.operators.coreos.com rhods-operator -n redhat-ods-operator &>/dev/null; then - oc delete subscription.operators.coreos.com rhods-operator \ - -n redhat-ods-operator --ignore-not-found --timeout=30s 2>&1 || true - echo " ✅ Subscription deleted" - else - echo " ℹ️ Subscription not found" - fi - echo "" - - # Step 4: Delete ClusterServiceVersion - # Per Red Hat docs: "Delete the ClusterServiceVersion object" - echo "Step 4: Deleting RHOAI CSV..." - CSV_NAME=$(oc get csv.operators.coreos.com -n redhat-ods-operator \ - -o name 2>/dev/null | grep rhods-operator || echo "") - if [ -n "$CSV_NAME" ]; then - oc delete "$CSV_NAME" -n redhat-ods-operator --ignore-not-found --timeout=60s 2>&1 || true - echo " ✅ CSV deleted: $CSV_NAME" - else - echo " ℹ️ CSV not found" - fi - echo "" - - # Step 5: Wait for operator pods to terminate - echo "Step 5: Waiting for RHOAI operator pods to terminate..." - if oc get namespace redhat-ods-operator &>/dev/null; then - oc wait --for=delete pod --all -n redhat-ods-operator --timeout=120s 2>/dev/null || { - echo " ⚠️ Timeout waiting for pods, force deleting..." - oc delete pod --all -n redhat-ods-operator --grace-period=0 --force 2>/dev/null || true - } - echo " ✅ Operator pods terminated" - else - echo " ℹ️ No operator pods found" - fi - echo "" - - # Step 6: Delete OperatorGroup - echo "Step 6: Deleting OperatorGroup..." - if oc get operatorgroup.operators.coreos.com rhods-operator -n redhat-ods-operator &>/dev/null; then - oc delete operatorgroup.operators.coreos.com rhods-operator \ - -n redhat-ods-operator --ignore-not-found --timeout=30s 2>&1 || true - echo " ✅ OperatorGroup deleted" - else - echo " ℹ️ OperatorGroup not found" - fi - echo "" - - # Step 7: Delete webhooks (prevents namespace termination deadlock) - echo "Step 7: Deleting RHOAI webhooks..." - - VALIDATING_WEBHOOKS=$(oc get validatingwebhookconfiguration -o name 2>/dev/null \ - | grep -E "opendatahub|kserve|rhoai" || echo "") - if [ -n "$VALIDATING_WEBHOOKS" ]; then - for webhook in $VALIDATING_WEBHOOKS; do - oc delete "$webhook" --ignore-not-found 2>&1 || true - echo " ✅ Deleted: $webhook" - done - else - echo " ℹ️ No validating webhooks found" - fi - - MUTATING_WEBHOOKS=$(oc get mutatingwebhookconfiguration -o name 2>/dev/null \ - | grep -E "opendatahub|kserve|rhoai" || echo "") - if [ -n "$MUTATING_WEBHOOKS" ]; then - for webhook in $MUTATING_WEBHOOKS; do - oc delete "$webhook" --ignore-not-found 2>&1 || true - echo " ✅ Deleted: $webhook" - done - else - echo " ℹ️ No mutating webhooks found" - fi - echo "" - - # Step 8: Delete RHOAI namespaces - echo "Step 8: Deleting RHOAI namespaces..." - - NAMESPACES=( - "redhat-ods-applications" - "redhat-ods-monitoring" - "redhat-ods-operator" - ) - - for ns in "${NAMESPACES[@]}"; do - if oc get namespace "$ns" &>/dev/null; then - echo " Deleting namespace: $ns" - oc delete namespace "$ns" --timeout=120s 2>&1 || { - echo " ⚠️ Timeout, removing finalizers..." - oc patch namespace "$ns" --type=json \ - -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true - oc patch namespace "$ns" --type=json \ - -p='[{"op": "remove", "path": "/spec/finalizers"}]' 2>/dev/null || true - oc delete namespace "$ns" --grace-period=0 --force 2>/dev/null || true - } - for i in {1..30}; do - if ! oc get namespace "$ns" &>/dev/null; then - echo " ✅ Namespace $ns deleted" - break - fi - if [ "$i" -eq 30 ]; then - echo " ⚠️ Namespace $ns still exists after cleanup attempts" - fi - sleep 2 - done - else - echo " ℹ️ Namespace $ns not found" - fi - done - echo "" - - # Step 9: Delete RHOAI CRDs - # Per Red Hat docs: "Delete the CRDs" - echo "Step 9: Deleting RHOAI CRDs..." - - safe_delete_crd() { - local crd_name=$1 - if oc get crd "$crd_name" &>/dev/null; then - echo " Deleting CRD: $crd_name" - oc delete crd "$crd_name" --timeout=60s --ignore-not-found 2>&1 || { - echo " ⚠️ Timeout, removing finalizers..." - oc patch crd "$crd_name" --type=json \ - -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true - oc delete crd "$crd_name" --ignore-not-found --grace-period=0 --force 2>/dev/null || true - } - echo " ✅ CRD $crd_name deleted" - fi - } - - RHOAI_CRDS=$(oc get crd -o name 2>/dev/null \ - | grep -E "opendatahub.io|kubeflow.org" || echo "") - if [ -n "$RHOAI_CRDS" ]; then - for crd in $RHOAI_CRDS; do - crd_name=$(echo "$crd" | sed 's|customresourcedefinition.apiextensions.k8s.io/||') - safe_delete_crd "$crd_name" - done - else - echo " ℹ️ No RHOAI CRDs found" - fi - echo "" - - # Step 10: Verification - echo "Step 10: Verifying cleanup..." - CLEANUP_SUCCESS=true - - for ns in "${NAMESPACES[@]}"; do - if oc get namespace "$ns" &>/dev/null; then - echo " ⚠️ Namespace $ns still exists" - CLEANUP_SUCCESS=false - fi - done - - if [ "$CLEANUP_SUCCESS" = true ]; then - echo " ✅ All namespaces removed" - fi - - REMAINING_WEBHOOKS=$(oc get validatingwebhookconfiguration,mutatingwebhookconfiguration \ - -o name 2>/dev/null | grep -E "opendatahub|kserve|rhoai" || echo "") - if [ -n "$REMAINING_WEBHOOKS" ]; then - echo " ⚠️ Some webhooks still exist:" - echo "$REMAINING_WEBHOOKS" | sed 's/^/ /' - CLEANUP_SUCCESS=false - else - echo " ✅ All webhooks removed" - fi - - echo "" - - if [ "$CLEANUP_SUCCESS" = true ]; then - echo "================================================" - echo "✅ RHOAI cleanup completed successfully" - echo "================================================" - else - echo "================================================" - echo "⚠️ RHOAI cleanup completed with warnings" - echo " Manual verification recommended" - echo "================================================" - exit 0 # Warnings are non-fatal; ArgoCD continues deletion - fi + - /scripts/delete-rhoai-resources.sh image: registry.redhat.io/openshift4/ose-cli:latest name: delete-rhoai-resources + volumeMounts: + - mountPath: /scripts + name: scripts restartPolicy: Never serviceAccountName: rhoai-cleanup - backoffLimit: 0 + terminationGracePeriodSeconds: 30 + volumes: + - configMap: + defaultMode: 0755 + name: rhoai-cleanup-script + name: scripts diff --git a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/kustomization.yaml b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/kustomization.yaml index efb6e649b..cb0b406c7 100644 --- a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/kustomization.yaml +++ b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/kustomization.yaml @@ -1,5 +1,26 @@ apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: + - files: + - delete-rhoai-resources.sh + name: rhoai-cleanup-script + namespace: openshift-gitops + +patches: + - patch: |- + - op: add + path: /metadata/annotations + value: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/hook-delete-policy: HookSucceeded,HookFailed + argocd.argoproj.io/sync-wave: "-1" + target: + kind: ConfigMap + name: rhoai-cleanup-script + resources: - delete-rhoai-resources.yaml diff --git a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/README.md b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/README.md index 2bbb1c1a5..c2a2dd96b 100644 --- a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/README.md +++ b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/README.md @@ -6,25 +6,20 @@ Without this component, ArgoCD deletes RHOAI manifests in an arbitrary order, wh ## What it does -Executes a 10-step cleanup Job before ArgoCD removes any resources: - -1. Delete the `DataScienceCluster` CR (triggers operator cleanup) -2. Wait 60 seconds for the operator to finish cleanup -3. Delete the operator `Subscription` -4. Delete the operator `ClusterServiceVersion` -5. Wait for operator pods to terminate -6. Delete the `OperatorGroup` -7. Delete all RHOAI `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration` resources -8. Delete RHOAI namespaces (`redhat-ods-operator`, `redhat-ods-applications`, `redhat-ods-monitoring`) -9. Delete RHOAI CRDs (`opendatahub.io`, `kubeflow.org`) -10. Verify cleanup and report status +Executes a 5-step cleanup Job before ArgoCD removes any resources: + +1. Delete all user workload CRs across all namespaces (InferenceServices, Notebooks, RayJobs, etc.) — prevents finalizer deadlocks +2. Delete the `DataScienceCluster` and `DSCInitialization` CRs +3. Delete OLM resources: `Subscription`, `ClusterServiceVersion`, `OperatorGroup`, `InstallPlan` +4. Delete RHOAI `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration` resources +5. Delete RHOAI namespaces and CRDs (explicit list per Red Hat documentation) ## How to enable Add this component to your instance overlay's `components:` list: ```yaml -# components/operators/openshift-ai/instance-2.x/overlays/stable-2.25-nvidia-gpu/kustomization.yaml +# components/operators/openshift-ai/instance-2.x/overlays/stable-2.25/kustomization.yaml components: - ../../components/cleanup-on-delete - ../../components/components-kserve @@ -35,6 +30,7 @@ components: All resources are ArgoCD `PreDelete` hooks and are automatically cleaned up after the Job completes: +- `ConfigMap/rhoai-cleanup-script` in `openshift-gitops` — mounts the cleanup script - `ServiceAccount/rhoai-cleanup` in `openshift-gitops` - `ClusterRole/rhoai-cleanup` - `ClusterRoleBinding/rhoai-cleanup` @@ -42,6 +38,7 @@ All resources are ArgoCD `PreDelete` hooks and are automatically cleaned up afte ## Notes -- `backoffLimit: 0` — the Job does not retry; partial cleanup is fine since remaining resources are deleted by ArgoCD -- The Job exits with code `0` even when warnings occur, so ArgoCD always proceeds with Application deletion +- The cleanup script is maintained in `delete-rhoai-resources.sh` and mounted into the Job via a ConfigMap +- `backoffLimit: 0` — the Job does not retry - The Job runs in `openshift-gitops` (not `redhat-ods-operator`) to avoid being killed when RHOAI namespaces are deleted +- The Job exits with code `0` even when resources are not found, so ArgoCD always proceeds with Application deletion diff --git a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.sh b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.sh new file mode 100644 index 000000000..8eb695bc5 --- /dev/null +++ b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.sh @@ -0,0 +1,303 @@ +#!/usr/bin/env bash + +set -e + +echo "================================================" +echo "Red Hat OpenShift AI Cleanup" +echo "================================================" +echo "" +echo "Following: docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed" +echo "" + +# Step 0: Delete user workload CRs across all namespaces before removing the DSC. +# Deleting CRs first prevents finalizer deadlocks during operator cleanup. +echo "Step 0: Deleting user workload CRs..." + +# Data Science Pipelines +oc delete datasciencepipelinesapplications.datasciencepipelinesapplications.opendatahub.io \ + -A --all --ignore-not-found 2>/dev/null || true +oc delete datasciencepipelines.components.platform.opendatahub.io \ + -A --all --ignore-not-found 2>/dev/null || true + +# Argo Workflows +oc delete clusterworkflowtemplates.argoproj.io --all --ignore-not-found 2>/dev/null || true +oc delete cronworkflows.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflowartifactgctasks.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workfloweventbindings.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflows.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflowtaskresults.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflowtasksets.argoproj.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workflowtemplates.argoproj.io -A --all --ignore-not-found 2>/dev/null || true + +# KServe +oc delete clusterstoragecontainers.serving.kserve.io --all --ignore-not-found 2>/dev/null || true +oc delete inferencegraphs.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete inferenceservices.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete llminferenceserviceconfigs.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete llminferenceservices.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete predictors.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete servingruntimes.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true +oc delete trainedmodels.serving.kserve.io -A --all --ignore-not-found 2>/dev/null || true + +# Inference (networking) +oc delete inferencemodels.inference.networking.x-k8s.io -A --all --ignore-not-found 2>/dev/null || true +oc delete inferencepools.inference.networking.x-k8s.io -A --all --ignore-not-found 2>/dev/null || true + +# Kueue +oc delete admissionchecks.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete clusterqueues.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete cohorts.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete localqueues.kueue.x-k8s.io -A --all --ignore-not-found 2>/dev/null || true +oc delete multikueueclusters.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete multikueueconfigs.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete provisioningrequestconfigs.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete resourceflavors.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete topologies.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete workloadpriorityclasses.kueue.x-k8s.io --all --ignore-not-found 2>/dev/null || true +oc delete workloads.kueue.x-k8s.io -A --all --ignore-not-found 2>/dev/null || true + +# Workbenches +oc delete notebooks.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true + +# Kubeflow Training Operator +oc delete jaxjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete mpijobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete mxjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete paddlejobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete pipelines.pipelines.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete pipelineversions.pipelines.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete pytorchjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete scheduledworkflows.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete tfjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete viewers.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true +oc delete xgboostjobs.kubeflow.org -A --all --ignore-not-found 2>/dev/null || true + +# TrustyAI +oc delete guardrailsorchestrators.trustyai.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete lmevaljobs.trustyai.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete nemoguardrails.trustyai.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete trustyaiservices.trustyai.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true + +# Ray +oc delete rayclusters.ray.io -A --all --ignore-not-found 2>/dev/null || true +oc delete rayjobs.ray.io -A --all --ignore-not-found 2>/dev/null || true +oc delete rayservices.ray.io -A --all --ignore-not-found 2>/dev/null || true + +# CodeFlare / AppWrappers +oc delete appwrappers.workload.codeflare.dev -A --all --ignore-not-found 2>/dev/null || true + +# Feast +oc delete featurestores.feast.dev -A --all --ignore-not-found 2>/dev/null || true + +# LlamaStack +oc delete llamastackdistributions.llamastack.io -A --all --ignore-not-found 2>/dev/null || true + +# Model Registry +oc delete modelregistries.modelregistry.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true + +# RHOAI / ODH platform CRs +oc delete accounts.nim.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete applications.app.k8s.io -A --all --ignore-not-found 2>/dev/null || true +oc delete auths.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete dashboards.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete feastoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete kserves.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete kueues.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete llamastackoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete modelcontrollers.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete modelmeshservings.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete modelregistries.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete monitorings.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete rays.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete servicemeshes.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete trainingoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete trustyais.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete workbenches.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true + +echo " Done" +echo "" + +# Step 1: Delete DataScienceCluster and DSCInitialization +echo "Step 1: Deleting DataScienceCluster and DSCInitialization..." +oc delete datascienceclusters.datasciencecluster.opendatahub.io --all --ignore-not-found 2>/dev/null || true +oc delete dscinitializations.dscinitialization.opendatahub.io --all --ignore-not-found 2>/dev/null || true +oc delete featuretrackers.features.opendatahub.io --all --ignore-not-found 2>/dev/null || true +echo " Done" +echo "" + +# Step 2: Delete OLM resources +echo "Step 2: Deleting Subscription, CSV, OperatorGroup, InstallPlan..." +oc delete subscription --all -n redhat-ods-operator --ignore-not-found +oc delete clusterserviceversion --all -n redhat-ods-operator --ignore-not-found +oc delete operatorgroup --all -n redhat-ods-operator --ignore-not-found +oc delete installplan --all -n redhat-ods-operator --ignore-not-found +echo " Done" +echo "" + +# Step 3: Delete webhooks +echo "Step 3: Deleting RHOAI webhooks..." +oc get validatingwebhookconfiguration -o name 2>/dev/null \ + | grep -E "opendatahub|kserve|rhoai" \ + | xargs -r oc delete --ignore-not-found 2>/dev/null || true +oc get mutatingwebhookconfiguration -o name 2>/dev/null \ + | grep -E "opendatahub|kserve|rhoai" \ + | xargs -r oc delete --ignore-not-found 2>/dev/null || true +echo " Done" +echo "" + +# Step 4: Delete RHOAI namespaces +echo "Step 4: Deleting RHOAI namespaces..." +oc delete namespace redhat-ods-applications --ignore-not-found 2>/dev/null || true +oc delete namespace redhat-ods-monitoring --ignore-not-found 2>/dev/null || true +oc delete namespace redhat-ods-operator --ignore-not-found 2>/dev/null || true +oc delete namespace rhods-notebooks --ignore-not-found 2>/dev/null || true +echo " Done" +echo "" + +# Step 5: Delete RHOAI CRDs +echo "Step 5: Deleting RHOAI CRDs..." + +# Data Science Pipelines +oc delete crd \ + datasciencepipelinesapplications.datasciencepipelinesapplications.opendatahub.io \ + datasciencepipelines.components.platform.opendatahub.io \ + --ignore-not-found 2>/dev/null || true + +# Argo Workflows +oc delete crd \ + clusterworkflowtemplates.argoproj.io \ + cronworkflows.argoproj.io \ + workflowartifactgctasks.argoproj.io \ + workfloweventbindings.argoproj.io \ + workflows.argoproj.io \ + workflowtaskresults.argoproj.io \ + workflowtasksets.argoproj.io \ + workflowtemplates.argoproj.io \ + --ignore-not-found 2>/dev/null || true + +# KServe +oc delete crd \ + clusterstoragecontainers.serving.kserve.io \ + inferencegraphs.serving.kserve.io \ + inferenceservices.serving.kserve.io \ + llminferenceserviceconfigs.serving.kserve.io \ + llminferenceservices.serving.kserve.io \ + predictors.serving.kserve.io \ + servingruntimes.serving.kserve.io \ + trainedmodels.serving.kserve.io \ + --ignore-not-found 2>/dev/null || true + +# Inference (networking) +oc delete crd \ + inferencemodels.inference.networking.x-k8s.io \ + inferencepools.inference.networking.x-k8s.io \ + --ignore-not-found 2>/dev/null || true + +# Kueue +oc delete crd \ + admissionchecks.kueue.x-k8s.io \ + clusterqueues.kueue.x-k8s.io \ + cohorts.kueue.x-k8s.io \ + localqueues.kueue.x-k8s.io \ + multikueueclusters.kueue.x-k8s.io \ + multikueueconfigs.kueue.x-k8s.io \ + provisioningrequestconfigs.kueue.x-k8s.io \ + resourceflavors.kueue.x-k8s.io \ + topologies.kueue.x-k8s.io \ + workloadpriorityclasses.kueue.x-k8s.io \ + workloads.kueue.x-k8s.io \ + --ignore-not-found 2>/dev/null || true + +# Workbenches / Kubeflow +oc delete crd \ + notebooks.kubeflow.org \ + viewers.kubeflow.org \ + --ignore-not-found 2>/dev/null || true + +# Kubeflow Training Operator +oc delete crd \ + jaxjobs.kubeflow.org \ + mpijobs.kubeflow.org \ + mxjobs.kubeflow.org \ + paddlejobs.kubeflow.org \ + pipelines.pipelines.kubeflow.org \ + pipelineversions.pipelines.kubeflow.org \ + pytorchjobs.kubeflow.org \ + scheduledworkflows.kubeflow.org \ + tfjobs.kubeflow.org \ + xgboostjobs.kubeflow.org \ + --ignore-not-found 2>/dev/null || true + +# TrustyAI +oc delete crd \ + guardrailsorchestrators.trustyai.opendatahub.io \ + lmevaljobs.trustyai.opendatahub.io \ + nemoguardrails.trustyai.opendatahub.io \ + trustyaiservices.trustyai.opendatahub.io \ + --ignore-not-found 2>/dev/null || true + +# Ray +oc delete crd \ + rayclusters.ray.io \ + rayjobs.ray.io \ + rayservices.ray.io \ + --ignore-not-found 2>/dev/null || true + +# CodeFlare +oc delete crd appwrappers.workload.codeflare.dev --ignore-not-found 2>/dev/null || true + +# Feast +oc delete crd \ + feastoperators.components.platform.opendatahub.io \ + featurestores.feast.dev \ + --ignore-not-found 2>/dev/null || true + +# LlamaStack +oc delete crd llamastackdistributions.llamastack.io --ignore-not-found 2>/dev/null || true + +# RHOAI / ODH platform components +oc delete crd \ + accounts.nim.opendatahub.io \ + applications.app.k8s.io \ + auths.services.platform.opendatahub.io \ + codeflares.components.platform.opendatahub.io \ + dashboards.components.platform.opendatahub.io \ + kserves.components.platform.opendatahub.io \ + kueues.components.platform.opendatahub.io \ + llamastackoperators.components.platform.opendatahub.io \ + mlflowoperators.components.platform.opendatahub.io \ + modelcontrollers.components.platform.opendatahub.io \ + modelmeshservings.components.platform.opendatahub.io \ + modelregistries.components.platform.opendatahub.io \ + modelregistries.modelregistry.opendatahub.io \ + monitorings.services.platform.opendatahub.io \ + rays.components.platform.opendatahub.io \ + servicemeshes.services.platform.opendatahub.io \ + trainingoperators.components.platform.opendatahub.io \ + trainers.components.platform.opendatahub.io \ + trustyais.components.platform.opendatahub.io \ + workbenches.components.platform.opendatahub.io \ + --ignore-not-found 2>/dev/null || true + +# RHOAI core +oc delete crd \ + acceleratorprofiles.dashboard.opendatahub.io \ + datascienceclusters.datasciencecluster.opendatahub.io \ + dscinitializations.dscinitialization.opendatahub.io \ + featuretrackers.features.opendatahub.io \ + gatewayconfigs.services.platform.opendatahub.io \ + hardwareprofiles.dashboard.opendatahub.io \ + hardwareprofiles.infrastructure.opendatahub.io \ + modelsasservices.components.platform.opendatahub.io \ + odhapplications.dashboard.opendatahub.io \ + odhdashboardconfigs.opendatahub.io \ + odhdocuments.dashboard.opendatahub.io \ + odhquickstarts.console.openshift.io \ + --ignore-not-found 2>/dev/null || true + +echo " Done" +echo "" + +echo "================================================" +echo "RHOAI cleanup completed" +echo "================================================" diff --git a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.yaml b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.yaml index ed060d500..a533f0ce5 100644 --- a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.yaml +++ b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.yaml @@ -20,13 +20,10 @@ rules: - "" resources: - namespaces - - pods verbs: - delete - get - list - - patch - - watch - apiGroups: - admissionregistration.k8s.io resources: @@ -44,23 +41,50 @@ rules: - delete - get - list - - patch - apiGroups: + - operators.coreos.com + resources: + - clusterserviceversions + - installplans + - operatorgroups + - subscriptions + verbs: + - delete + - get + - list + - apiGroups: + - components.platform.opendatahub.io + - dashboard.opendatahub.io + - datasciencecluster.opendatahub.io + - datasciencepipelinesapplications.opendatahub.io + - dscinitialization.opendatahub.io + - features.opendatahub.io + - infrastructure.opendatahub.io + - modelregistry.opendatahub.io + - nim.opendatahub.io - opendatahub.io + - services.platform.opendatahub.io + - trustyai.opendatahub.io resources: - - datascienceclusters - - dscinitializations + - "*" verbs: - delete - get - list - - patch - apiGroups: - - operators.coreos.com + - app.k8s.io + - argoproj.io + - console.openshift.io + - feast.dev + - inference.networking.x-k8s.io + - kubeflow.org + - kueue.x-k8s.io + - llamastack.io + - ray.io + - serving.kserve.io + - workload.codeflare.dev resources: - - clusterserviceversions - - operatorgroups - - subscriptions + - "*" verbs: - delete - get @@ -93,6 +117,7 @@ metadata: name: delete-rhoai-resources namespace: openshift-gitops spec: + backoffLimit: 0 template: metadata: name: delete-rhoai-resources @@ -100,225 +125,17 @@ spec: containers: - command: - /bin/bash - - -c - - | - set -e - - echo "================================================" - echo "Red Hat OpenShift AI Cleanup - Official Procedure" - echo "================================================" - echo "" - echo "Following Red Hat documentation:" - echo "docs.redhat.com/en/documentation/red_hat_openshift_ai_self-managed" - echo "" - echo "This PreDelete hook runs BEFORE ArgoCD deletes Application resources," - echo "ensuring proper RHOAI deletion order per Red Hat documentation." - echo "" - - # Step 1: Delete the DataScienceCluster CR - # Per Red Hat docs: "Delete the DataScienceCluster instance" - echo "Step 1: Deleting DataScienceCluster CR..." - if oc get datasciencecluster.opendatahub.io default-dsc &>/dev/null; then - oc delete datasciencecluster.opendatahub.io default-dsc --timeout=120s --ignore-not-found 2>&1 || { - echo " ⚠️ Deletion timed out, removing finalizers..." - oc patch datasciencecluster.opendatahub.io default-dsc --type=json \ - -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true - oc delete datasciencecluster.opendatahub.io default-dsc \ - --ignore-not-found --grace-period=0 --force 2>/dev/null || true - } - echo " ✅ DataScienceCluster deleted" - else - echo " ℹ️ DataScienceCluster not found" - fi - echo "" - - # Step 2: Wait for operator cleanup - echo "Step 2: Waiting for RHOAI operator cleanup (60s grace period)..." - sleep 60 - echo " ✅ Grace period completed" - echo "" - - # Step 3: Delete RHOAI operator Subscription - # Per Red Hat docs: "Delete the Subscription object" - echo "Step 3: Deleting RHOAI operator Subscription..." - if oc get subscription.operators.coreos.com rhods-operator -n redhat-ods-operator &>/dev/null; then - oc delete subscription.operators.coreos.com rhods-operator \ - -n redhat-ods-operator --ignore-not-found --timeout=30s 2>&1 || true - echo " ✅ Subscription deleted" - else - echo " ℹ️ Subscription not found" - fi - echo "" - - # Step 4: Delete ClusterServiceVersion - # Per Red Hat docs: "Delete the ClusterServiceVersion object" - echo "Step 4: Deleting RHOAI CSV..." - CSV_NAME=$(oc get csv.operators.coreos.com -n redhat-ods-operator \ - -o name 2>/dev/null | grep rhods-operator || echo "") - if [ -n "$CSV_NAME" ]; then - oc delete "$CSV_NAME" -n redhat-ods-operator --ignore-not-found --timeout=60s 2>&1 || true - echo " ✅ CSV deleted: $CSV_NAME" - else - echo " ℹ️ CSV not found" - fi - echo "" - - # Step 5: Wait for operator pods to terminate - echo "Step 5: Waiting for RHOAI operator pods to terminate..." - if oc get namespace redhat-ods-operator &>/dev/null; then - oc wait --for=delete pod --all -n redhat-ods-operator --timeout=120s 2>/dev/null || { - echo " ⚠️ Timeout waiting for pods, force deleting..." - oc delete pod --all -n redhat-ods-operator --grace-period=0 --force 2>/dev/null || true - } - echo " ✅ Operator pods terminated" - else - echo " ℹ️ No operator pods found" - fi - echo "" - - # Step 6: Delete OperatorGroup - echo "Step 6: Deleting OperatorGroup..." - if oc get operatorgroup.operators.coreos.com rhods-operator -n redhat-ods-operator &>/dev/null; then - oc delete operatorgroup.operators.coreos.com rhods-operator \ - -n redhat-ods-operator --ignore-not-found --timeout=30s 2>&1 || true - echo " ✅ OperatorGroup deleted" - else - echo " ℹ️ OperatorGroup not found" - fi - echo "" - - # Step 7: Delete webhooks (prevents namespace termination deadlock) - echo "Step 7: Deleting RHOAI webhooks..." - - VALIDATING_WEBHOOKS=$(oc get validatingwebhookconfiguration -o name 2>/dev/null \ - | grep -E "opendatahub|kserve|rhoai" || echo "") - if [ -n "$VALIDATING_WEBHOOKS" ]; then - for webhook in $VALIDATING_WEBHOOKS; do - oc delete "$webhook" --ignore-not-found 2>&1 || true - echo " ✅ Deleted: $webhook" - done - else - echo " ℹ️ No validating webhooks found" - fi - - MUTATING_WEBHOOKS=$(oc get mutatingwebhookconfiguration -o name 2>/dev/null \ - | grep -E "opendatahub|kserve|rhoai" || echo "") - if [ -n "$MUTATING_WEBHOOKS" ]; then - for webhook in $MUTATING_WEBHOOKS; do - oc delete "$webhook" --ignore-not-found 2>&1 || true - echo " ✅ Deleted: $webhook" - done - else - echo " ℹ️ No mutating webhooks found" - fi - echo "" - - # Step 8: Delete RHOAI namespaces - echo "Step 8: Deleting RHOAI namespaces..." - - NAMESPACES=( - "redhat-ods-applications" - "redhat-ods-monitoring" - "redhat-ods-operator" - ) - - for ns in "${NAMESPACES[@]}"; do - if oc get namespace "$ns" &>/dev/null; then - echo " Deleting namespace: $ns" - oc delete namespace "$ns" --timeout=120s 2>&1 || { - echo " ⚠️ Timeout, removing finalizers..." - oc patch namespace "$ns" --type=json \ - -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true - oc patch namespace "$ns" --type=json \ - -p='[{"op": "remove", "path": "/spec/finalizers"}]' 2>/dev/null || true - oc delete namespace "$ns" --grace-period=0 --force 2>/dev/null || true - } - for i in {1..30}; do - if ! oc get namespace "$ns" &>/dev/null; then - echo " ✅ Namespace $ns deleted" - break - fi - if [ "$i" -eq 30 ]; then - echo " ⚠️ Namespace $ns still exists after cleanup attempts" - fi - sleep 2 - done - else - echo " ℹ️ Namespace $ns not found" - fi - done - echo "" - - # Step 9: Delete RHOAI CRDs - # Per Red Hat docs: "Delete the CRDs" - echo "Step 9: Deleting RHOAI CRDs..." - - safe_delete_crd() { - local crd_name=$1 - if oc get crd "$crd_name" &>/dev/null; then - echo " Deleting CRD: $crd_name" - oc delete crd "$crd_name" --timeout=60s --ignore-not-found 2>&1 || { - echo " ⚠️ Timeout, removing finalizers..." - oc patch crd "$crd_name" --type=json \ - -p='[{"op": "remove", "path": "/metadata/finalizers"}]' 2>/dev/null || true - oc delete crd "$crd_name" --ignore-not-found --grace-period=0 --force 2>/dev/null || true - } - echo " ✅ CRD $crd_name deleted" - fi - } - - RHOAI_CRDS=$(oc get crd -o name 2>/dev/null \ - | grep -E "opendatahub.io|kubeflow.org" || echo "") - if [ -n "$RHOAI_CRDS" ]; then - for crd in $RHOAI_CRDS; do - crd_name=$(echo "$crd" | sed 's|customresourcedefinition.apiextensions.k8s.io/||') - safe_delete_crd "$crd_name" - done - else - echo " ℹ️ No RHOAI CRDs found" - fi - echo "" - - # Step 10: Verification - echo "Step 10: Verifying cleanup..." - CLEANUP_SUCCESS=true - - for ns in "${NAMESPACES[@]}"; do - if oc get namespace "$ns" &>/dev/null; then - echo " ⚠️ Namespace $ns still exists" - CLEANUP_SUCCESS=false - fi - done - - if [ "$CLEANUP_SUCCESS" = true ]; then - echo " ✅ All namespaces removed" - fi - - REMAINING_WEBHOOKS=$(oc get validatingwebhookconfiguration,mutatingwebhookconfiguration \ - -o name 2>/dev/null | grep -E "opendatahub|kserve|rhoai" || echo "") - if [ -n "$REMAINING_WEBHOOKS" ]; then - echo " ⚠️ Some webhooks still exist:" - echo "$REMAINING_WEBHOOKS" | sed 's/^/ /' - CLEANUP_SUCCESS=false - else - echo " ✅ All webhooks removed" - fi - - echo "" - - if [ "$CLEANUP_SUCCESS" = true ]; then - echo "================================================" - echo "✅ RHOAI cleanup completed successfully" - echo "================================================" - else - echo "================================================" - echo "⚠️ RHOAI cleanup completed with warnings" - echo " Manual verification recommended" - echo "================================================" - exit 0 # Warnings are non-fatal; ArgoCD continues deletion - fi + - /scripts/delete-rhoai-resources.sh image: registry.redhat.io/openshift4/ose-cli:latest name: delete-rhoai-resources + volumeMounts: + - mountPath: /scripts + name: scripts restartPolicy: Never serviceAccountName: rhoai-cleanup - backoffLimit: 0 + terminationGracePeriodSeconds: 30 + volumes: + - configMap: + defaultMode: 0755 + name: rhoai-cleanup-script + name: scripts diff --git a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/kustomization.yaml b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/kustomization.yaml index efb6e649b..cb0b406c7 100644 --- a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/kustomization.yaml +++ b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/kustomization.yaml @@ -1,5 +1,26 @@ apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: + - files: + - delete-rhoai-resources.sh + name: rhoai-cleanup-script + namespace: openshift-gitops + +patches: + - patch: |- + - op: add + path: /metadata/annotations + value: + argocd.argoproj.io/hook: PreDelete + argocd.argoproj.io/hook-delete-policy: HookSucceeded,HookFailed + argocd.argoproj.io/sync-wave: "-1" + target: + kind: ConfigMap + name: rhoai-cleanup-script + resources: - delete-rhoai-resources.yaml From 2c15f319822e60cabe78b6c57e0e812cebebaa11 Mon Sep 17 00:00:00 2001 From: Laurent TOURREAU Date: Tue, 12 May 2026 22:37:02 +0200 Subject: [PATCH 4/5] Add CRs, InferenceServices, RayJobs to spell check wordlist Co-Authored-By: Claude Sonnet 4.6 --- .wordlist-txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.wordlist-txt b/.wordlist-txt index 1081fcf4b..f591637b0 100644 --- a/.wordlist-txt +++ b/.wordlist-txt @@ -22,6 +22,7 @@ CICD CNI CRD CRDs +CRs CUDA CheCluster CloudEvents @@ -73,6 +74,7 @@ ITMS IgnoreExtraneous ImageSetConfiguration ImageStreams +InferenceServices Infiniband Istio Jaeger @@ -137,6 +139,7 @@ README RHACM RHOAI RawDeployments +RayJobs Rebasing RoleBinding Rollout From 0950f4e4738a562ec9fc8a76946956a595d561ea Mon Sep 17 00:00:00 2001 From: Laurent TOURREAU Date: Thu, 21 May 2026 16:24:31 +0200 Subject: [PATCH 5/5] Add sparkoperators CRD introduced in RHOAI 3.4.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated on OCP cluster running rhods-operator.3.4.0 — the CRD sparkoperators.components.platform.opendatahub.io was not in the explicit list and remained after cleanup. Added to both Step 0 (CR deletion) and Step 5 (CRD deletion) in instance-2.x and instance-3.x. Co-Authored-By: Claude Sonnet 4.6 --- .../components/cleanup-on-delete/delete-rhoai-resources.sh | 2 ++ .../components/cleanup-on-delete/delete-rhoai-resources.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.sh b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.sh index 8eb695bc5..44d6eb246 100644 --- a/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.sh +++ b/components/operators/openshift-ai/instance-2.x/components/cleanup-on-delete/delete-rhoai-resources.sh @@ -110,6 +110,7 @@ oc delete modelregistries.components.platform.opendatahub.io -A --all --ignore-n oc delete monitorings.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete rays.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete servicemeshes.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete sparkoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete trainingoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete trustyais.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete workbenches.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true @@ -273,6 +274,7 @@ oc delete crd \ monitorings.services.platform.opendatahub.io \ rays.components.platform.opendatahub.io \ servicemeshes.services.platform.opendatahub.io \ + sparkoperators.components.platform.opendatahub.io \ trainingoperators.components.platform.opendatahub.io \ trainers.components.platform.opendatahub.io \ trustyais.components.platform.opendatahub.io \ diff --git a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.sh b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.sh index 8eb695bc5..44d6eb246 100644 --- a/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.sh +++ b/components/operators/openshift-ai/instance-3.x/components/cleanup-on-delete/delete-rhoai-resources.sh @@ -110,6 +110,7 @@ oc delete modelregistries.components.platform.opendatahub.io -A --all --ignore-n oc delete monitorings.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete rays.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete servicemeshes.services.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true +oc delete sparkoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete trainingoperators.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete trustyais.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true oc delete workbenches.components.platform.opendatahub.io -A --all --ignore-not-found 2>/dev/null || true @@ -273,6 +274,7 @@ oc delete crd \ monitorings.services.platform.opendatahub.io \ rays.components.platform.opendatahub.io \ servicemeshes.services.platform.opendatahub.io \ + sparkoperators.components.platform.opendatahub.io \ trainingoperators.components.platform.opendatahub.io \ trainers.components.platform.opendatahub.io \ trustyais.components.platform.opendatahub.io \