@@ -11,10 +11,12 @@ import (
1111 "slices"
1212
1313 "github.com/davecgh/go-spew/spew"
14+ "github.com/hashicorp/go-multierror"
1415 routev1 "github.com/openshift/api/route/v1"
1516 "gopkg.in/yaml.v3"
1617 corev1 "k8s.io/api/core/v1"
1718 k8serr "k8s.io/apimachinery/pkg/api/errors"
19+ "k8s.io/apimachinery/pkg/api/meta"
1820 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1921 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2022 "k8s.io/apimachinery/pkg/runtime"
@@ -23,10 +25,23 @@ import (
2325 "k8s.io/client-go/discovery"
2426 "sigs.k8s.io/controller-runtime/pkg/client"
2527 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
28+ logf "sigs.k8s.io/controller-runtime/pkg/log"
2629)
2730
2831const PlatformFieldOwner = "platform.opendatahub.io"
2932
33+ // ResourceSpec defines a specification for identifying and filtering Kubernetes resources
34+ // based on their GroupVersionKind, namespace, and field values.
35+ type ResourceSpec struct {
36+ Gvk schema.GroupVersionKind
37+ Namespace string
38+ // FieldPath specifies the path to the field for filtering, like ["metadata", "name"]
39+ FieldPath []string
40+ // FilterValues contains the values to match against the field - if the field value
41+ // matches any of these values, the resource will be processed (e.g., deleted)
42+ FilterValues []string
43+ }
44+
3045func ToUnstructured (obj any ) (* unstructured.Unstructured , error ) {
3146 data , err := runtime .DefaultUnstructuredConverter .ToUnstructured (obj )
3247 if err != nil {
@@ -631,3 +646,95 @@ func ListAvailableAPIResources(
631646
632647 return items , nil
633648}
649+
650+ // DeleteResources iterates through a list of ResourceSpec and deletes matching Kubernetes resources.
651+ // It collects all errors encountered during deletion and returns them as a multierror.
652+ //
653+ // Parameters:
654+ // - ctx: The context for the operation
655+ // - c: The Kubernetes client used to list and delete resources
656+ // - resources: A slice of ResourceSpec defining which resources to delete
657+ //
658+ // Returns:
659+ // - error: A multierror containing all errors encountered, or nil if all deletions succeeded
660+ func DeleteResources (ctx context.Context , c client.Client , resources []ResourceSpec ) error {
661+ var errors * multierror.Error
662+
663+ for _ , res := range resources {
664+ err := DeleteOneResource (ctx , c , res )
665+ errors = multierror .Append (errors , err )
666+ }
667+
668+ return errors .ErrorOrNil ()
669+ }
670+
671+ // DeleteOneResource deletes all Kubernetes resources matching the given ResourceSpec.
672+ // It lists resources of the specified GVK in the given namespace, filters them based on
673+ // the field path and values, and deletes matching resources.
674+ //
675+ // Parameters:
676+ // - ctx: The context for the operation
677+ // - c: The Kubernetes client used to list and delete resources
678+ // - res: The ResourceSpec defining which resources to delete
679+ //
680+ // Returns:
681+ // - error: An error if the operation fails, or nil on success
682+ func DeleteOneResource (ctx context.Context , c client.Client , res ResourceSpec ) error {
683+ log := logf .FromContext (ctx )
684+ list := & unstructured.UnstructuredList {}
685+ list .SetGroupVersionKind (res .Gvk )
686+
687+ err := c .List (ctx , list , client .InNamespace (res .Namespace ))
688+ if err != nil {
689+ if meta .IsNoMatchError (err ) {
690+ log .Info ("CRD not found, will not delete" , "gvk" , res .Gvk .String ())
691+ return nil
692+ }
693+ return fmt .Errorf ("failed to list %s: %w" , res .Gvk .Kind , err )
694+ }
695+
696+ for _ , item := range list .Items {
697+ v , ok , err := unstructured .NestedString (item .Object , res .FieldPath ... )
698+ if err != nil {
699+ return fmt .Errorf ("failed to get field %v for %s %s/%s: %w" , res .FieldPath , res .Gvk .Kind , res .Namespace , item .GetName (), err )
700+ }
701+
702+ if ! ok {
703+ return fmt .Errorf ("unexisting path to delete: %v" , res .FieldPath )
704+ }
705+
706+ for _ , targetValue := range res .FilterValues {
707+ if v == targetValue {
708+ err = c .Delete (ctx , & item )
709+ if err != nil {
710+ return fmt .Errorf ("failed to delete %s %s/%s: %w" , res .Gvk .Kind , res .Namespace , item .GetName (), err )
711+ }
712+ log .Info ("Deleted object" , "name" , item .GetName (), "gvk" , res .Gvk .String (), "namespace" , res .Namespace )
713+ }
714+ }
715+ }
716+
717+ return nil
718+ }
719+
720+ // UnsetOwnerReferences removes all owner references from a Kubernetes object and updates it.
721+ // This is useful when you need to orphan a resource from its owner.
722+ //
723+ // Parameters:
724+ // - ctx: The context for the operation
725+ // - cli: The Kubernetes client used to update the resource
726+ // - instanceName: The name of the instance (used for error reporting)
727+ // - odhObject: The unstructured object whose owner references should be removed
728+ //
729+ // Returns:
730+ // - error: An error if the update operation fails, or nil if there are no owner references or update succeeds
731+ func UnsetOwnerReferences (ctx context.Context , cli client.Client , instanceName string , odhObject * unstructured.Unstructured ) error {
732+ if odhObject .GetOwnerReferences () != nil {
733+ // set to nil as updates
734+ odhObject .SetOwnerReferences (nil )
735+ if err := cli .Update (ctx , odhObject ); err != nil {
736+ return fmt .Errorf ("error unset ownerreference for CR %s : %w" , instanceName , err )
737+ }
738+ }
739+ return nil
740+ }
0 commit comments