@@ -18,12 +18,16 @@ package controllers
1818
1919import (
2020 "context"
21+ "strings"
22+ "time"
2123
2224 "github.com/go-logr/logr"
2325 "github.com/pkg/errors"
2426
2527 apierrors "k8s.io/apimachinery/pkg/api/errors"
28+ "k8s.io/apimachinery/pkg/api/meta"
2629 "k8s.io/apimachinery/pkg/runtime"
30+ kerrors "k8s.io/apimachinery/pkg/util/errors"
2731 "sigs.k8s.io/cluster-api/util"
2832 ctrl "sigs.k8s.io/controller-runtime"
2933 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -32,6 +36,7 @@ import (
3236
3337 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta1"
3438 "sigs.k8s.io/cluster-api-provider-ibmcloud/cloud/scope"
39+ clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3540)
3641
3742// IBMPowerVSClusterReconciler reconciles a IBMPowerVSCluster object
@@ -85,7 +90,7 @@ func (r *IBMPowerVSClusterReconciler) Reconcile(ctx context.Context, req ctrl.Re
8590
8691 // Handle deleted clusters
8792 if ! ibmCluster .DeletionTimestamp .IsZero () {
88- return r .reconcileDelete (clusterScope )
93+ return r .reconcileDelete (ctx , clusterScope )
8994 }
9095
9196 if err != nil {
@@ -105,11 +110,127 @@ func (r *IBMPowerVSClusterReconciler) reconcile(ctx context.Context, clusterScop
105110 return ctrl.Result {}, nil
106111}
107112
108- func (r * IBMPowerVSClusterReconciler ) reconcileDelete (clusterScope * scope.PowerVSClusterScope ) (ctrl.Result , error ) {
109- controllerutil .RemoveFinalizer (clusterScope .IBMPowerVSCluster , v1beta1 .IBMPowerVSClusterFinalizer )
113+ func (r * IBMPowerVSClusterReconciler ) reconcileDelete (ctx context.Context , clusterScope * scope.PowerVSClusterScope ) (ctrl.Result , error ) {
114+ log := ctrl .LoggerFrom (ctx )
115+
116+ cluster := clusterScope .IBMPowerVSCluster
117+ descendants , err := r .listDescendants (ctx , cluster )
118+ if err != nil {
119+ log .Error (err , "Failed to list descendants" )
120+ return reconcile.Result {}, err
121+ }
122+
123+ children , err := descendants .filterOwnedDescendants (cluster )
124+ if err != nil {
125+ log .Error (err , "Failed to extract direct descendants" )
126+ return reconcile.Result {}, err
127+ }
128+
129+ if len (children ) > 0 {
130+ log .Info ("Cluster still has children - deleting them first" , "count" , len (children ))
131+
132+ var errs []error
133+
134+ for _ , child := range children {
135+ if ! child .GetDeletionTimestamp ().IsZero () {
136+ // Don't handle deleted child
137+ continue
138+ }
139+ gvk := child .GetObjectKind ().GroupVersionKind ().String ()
140+
141+ log .Info ("Deleting child object" , "gvk" , gvk , "name" , child .GetName ())
142+ if err := r .Client .Delete (ctx , child ); err != nil {
143+ err = errors .Wrapf (err , "error deleting cluster %s/%s: failed to delete %s %s" , cluster .Namespace , cluster .Name , gvk , child .GetName ())
144+ log .Error (err , "Error deleting resource" , "gvk" , gvk , "name" , child .GetName ())
145+ errs = append (errs , err )
146+ }
147+ }
148+
149+ if len (errs ) > 0 {
150+ return ctrl.Result {}, kerrors .NewAggregate (errs )
151+ }
152+ }
153+
154+ if descendantCount := descendants .length (); descendantCount > 0 {
155+ indirect := descendantCount - len (children )
156+ log .Info ("Cluster still has descendants - need to requeue" , "descendants" , descendants .descendantNames (), "indirect descendants count" , indirect )
157+ // Requeue so we can check the next time to see if there are still any descendants left.
158+ return ctrl.Result {RequeueAfter : 5 * time .Second }, nil
159+ }
160+
161+ controllerutil .RemoveFinalizer (cluster , v1beta1 .IBMPowerVSClusterFinalizer )
110162 return ctrl.Result {}, nil
111163}
112164
165+ type clusterDescendants struct {
166+ ibmPowerVSImages v1beta1.IBMPowerVSImageList
167+ }
168+
169+ // length returns the number of descendants.
170+ func (c * clusterDescendants ) length () int {
171+ return len (c .ibmPowerVSImages .Items )
172+ }
173+
174+ func (c * clusterDescendants ) descendantNames () string {
175+ descendants := make ([]string , 0 )
176+ ibmPowerVSImageNames := make ([]string , len (c .ibmPowerVSImages .Items ))
177+ for i , ibmPowerVSImage := range c .ibmPowerVSImages .Items {
178+ ibmPowerVSImageNames [i ] = ibmPowerVSImage .Name
179+ }
180+ if len (ibmPowerVSImageNames ) > 0 {
181+ descendants = append (descendants , "IBM Powervs Images: " + strings .Join (ibmPowerVSImageNames , "," ))
182+ }
183+
184+ return strings .Join (descendants , ";" )
185+ }
186+
187+ // listDescendants returns a list of all IBMPowerVSImages for the cluster.
188+ func (r * IBMPowerVSClusterReconciler ) listDescendants (ctx context.Context , cluster * v1beta1.IBMPowerVSCluster ) (clusterDescendants , error ) {
189+ var descendants clusterDescendants
190+
191+ listOptions := []client.ListOption {
192+ client .InNamespace (cluster .Namespace ),
193+ client .MatchingLabels (map [string ]string {clusterv1 .ClusterLabelName : cluster .Name }),
194+ }
195+
196+ if err := r .Client .List (ctx , & descendants .ibmPowerVSImages , listOptions ... ); err != nil {
197+ return descendants , errors .Wrapf (err , "failed to list IBMPowerVSImages for cluster %s/%s" , cluster .Namespace , cluster .Name )
198+ }
199+
200+ return descendants , nil
201+ }
202+
203+ // filterOwnedDescendants returns an array of runtime.Objects containing only those descendants that have the cluster
204+ // as an owner reference.
205+ func (c clusterDescendants ) filterOwnedDescendants (cluster * v1beta1.IBMPowerVSCluster ) ([]client.Object , error ) {
206+ var ownedDescendants []client.Object
207+ eachFunc := func (o runtime.Object ) error {
208+ obj := o .(client.Object )
209+ acc , err := meta .Accessor (obj )
210+ if err != nil {
211+ return nil //nolint:nilerr // We don't want to exit the EachListItem loop, just continue
212+ }
213+
214+ if util .IsOwnedByObject (acc , cluster ) {
215+ ownedDescendants = append (ownedDescendants , obj )
216+ }
217+
218+ return nil
219+ }
220+
221+ lists := []client.ObjectList {
222+ & c .ibmPowerVSImages ,
223+ }
224+
225+ for _ , list := range lists {
226+ if err := meta .EachListItem (list , eachFunc ); err != nil {
227+ return nil , errors .Wrapf (err , "error finding owned descendants of cluster %s/%s" , cluster .Namespace , cluster .Name )
228+ }
229+ }
230+
231+ return ownedDescendants , nil
232+ }
233+
113234// SetupWithManager creates a new IBMPowerVSCluster controller for a manager.
114235func (r * IBMPowerVSClusterReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
115236 return ctrl .NewControllerManagedBy (mgr ).
0 commit comments