@@ -4,17 +4,20 @@ package controller
44
55import (
66 "context"
7+ "errors"
78 "fmt"
89 "slices"
910
1011 apierrors "k8s.io/apimachinery/pkg/api/errors"
1112 apimeta "k8s.io/apimachinery/pkg/api/meta"
1213 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1314 "k8s.io/apimachinery/pkg/types"
15+ kerrors "k8s.io/apimachinery/pkg/util/errors"
1416 ctrl "sigs.k8s.io/controller-runtime"
1517 "sigs.k8s.io/controller-runtime/pkg/client"
1618 "sigs.k8s.io/controller-runtime/pkg/cluster"
1719 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
20+ "sigs.k8s.io/controller-runtime/pkg/finalizer"
1821 "sigs.k8s.io/controller-runtime/pkg/handler"
1922 "sigs.k8s.io/controller-runtime/pkg/log"
2023 "sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -32,7 +35,8 @@ import (
3235
3336// WorkloadDeploymentReconciler reconciles a WorkloadDeployment object
3437type WorkloadDeploymentReconciler struct {
35- mgr mcmanager.Manager
38+ mgr mcmanager.Manager
39+ finalizers finalizer.Finalizers
3640}
3741
3842// +kubebuilder:rbac:groups=compute.datumapis.com,resources=workloaddeployments,verbs=get;list;watch;create;update;patch;delete
@@ -57,6 +61,24 @@ func (r *WorkloadDeploymentReconciler) Reconcile(ctx context.Context, req mcreco
5761 return ctrl.Result {}, err
5862 }
5963
64+ finalizationResult , err := r .finalizers .Finalize (ctx , & deployment )
65+ if err != nil {
66+ if v , ok := err .(kerrors.Aggregate ); ok && v .Is (errDeploymentHasInstances ) {
67+ // Don't produce an error in this case and let the watch on deployments
68+ // result in another reconcile schedule.
69+ logger .Info ("deployment still has instances, waiting until removal" )
70+ return ctrl.Result {}, nil
71+ } else {
72+ return ctrl.Result {}, fmt .Errorf ("failed to finalize: %w" , err )
73+ }
74+ }
75+ if finalizationResult .Updated {
76+ if err = cl .GetClient ().Update (ctx , & deployment ); err != nil {
77+ return ctrl.Result {}, fmt .Errorf ("failed to update based on finalization result: %w" , err )
78+ }
79+ return ctrl.Result {}, nil
80+ }
81+
6082 if ! deployment .DeletionTimestamp .IsZero () {
6183 return ctrl.Result {}, nil
6284 }
@@ -332,10 +354,58 @@ func (r *WorkloadDeploymentReconciler) reconcileNetworks(
332354 return result
333355}
334356
357+ var errDeploymentHasInstances = errors .New ("deployment has instances" )
358+
359+ func (r * WorkloadDeploymentReconciler ) Finalize (ctx context.Context , obj client.Object ) (finalizer.Result , error ) {
360+ clusterName , ok := mccontext .ClusterFrom (ctx )
361+ if ! ok {
362+ return finalizer.Result {}, fmt .Errorf ("cluster name not found in context" )
363+ }
364+
365+ cl , err := r .mgr .GetCluster (ctx , clusterName )
366+ if err != nil {
367+ return finalizer.Result {}, err
368+ }
369+
370+ var instanceList computev1alpha.InstanceList
371+ listOpts := []client.ListOption {
372+ client .InNamespace (obj .GetNamespace ()),
373+ client.MatchingLabels {
374+ computev1alpha .WorkloadDeploymentUIDLabel : string (obj .GetUID ()),
375+ },
376+ }
377+
378+ if err := cl .GetClient ().List (ctx , & instanceList , listOpts ... ); err != nil {
379+ return finalizer.Result {}, fmt .Errorf ("failed listing instances: %w" , err )
380+ }
381+
382+ if len (instanceList .Items ) == 0 {
383+ log .FromContext (ctx ).Info ("instances have been removed" )
384+ return finalizer.Result {}, nil
385+ }
386+
387+ // All instances need to be deleted before the deployment may be deleted
388+ for _ , instance := range instanceList .Items {
389+ if instance .DeletionTimestamp .IsZero () {
390+ if err := cl .GetClient ().Delete (ctx , & instance ); client .IgnoreNotFound (err ) != nil {
391+ return finalizer.Result {}, fmt .Errorf ("failed deleting instance: %w" , err )
392+ }
393+ }
394+ }
395+
396+ // Really don't like using errors for communication here. I think we'd need
397+ // to move away from the finalizer helper to ensure we can wait on child
398+ // resources to be gone before allowing the finalizer to be removed.
399+ return finalizer.Result {}, errDeploymentHasInstances
400+ }
401+
335402// SetupWithManager sets up the controller with the Manager.
336403func (r * WorkloadDeploymentReconciler ) SetupWithManager (mgr mcmanager.Manager ) error {
337404 r .mgr = mgr
338- // TODO(jreese) finalizers
405+ r .finalizers = finalizer .NewFinalizers ()
406+ if err := r .finalizers .Register (workloadControllerFinalizer , r ); err != nil {
407+ return fmt .Errorf ("failed to register finalizer: %w" , err )
408+ }
339409 return mcbuilder .ControllerManagedBy (mgr ).
340410 For (& computev1alpha.WorkloadDeployment {}, mcbuilder .WithEngageWithLocalCluster (false )).
341411 Owns (& computev1alpha.Instance {}).
0 commit comments