diff --git a/api/v1alpha2/agentpool_helpers.go b/api/v1alpha2/agentpool_helpers.go index de7d3ecb..68f59311 100644 --- a/api/v1alpha2/agentpool_helpers.go +++ b/api/v1alpha2/agentpool_helpers.go @@ -3,18 +3,6 @@ package v1alpha2 -import ( - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -func (ap *AgentPool) NeedToAddFinalizer(finalizer string) bool { - return ap.ObjectMeta.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(ap, finalizer) -} - -func (ap *AgentPool) IsDeletionCandidate(finalizer string) bool { - return !ap.ObjectMeta.DeletionTimestamp.IsZero() && controllerutil.ContainsFinalizer(ap, finalizer) -} - func (ap *AgentPool) IsCreationCandidate() bool { return ap.Status.AgentPoolID == "" } diff --git a/api/v1alpha2/module_helpers.go b/api/v1alpha2/module_helpers.go deleted file mode 100644 index 9ba0cf5c..00000000 --- a/api/v1alpha2/module_helpers.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha2 - -import ( - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -func (m *Module) NeedToAddFinalizer(finalizer string) bool { - return m.ObjectMeta.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(m, finalizer) -} - -func (m *Module) IsDeletionCandidate(finalizer string) bool { - return !m.ObjectMeta.DeletionTimestamp.IsZero() && controllerutil.ContainsFinalizer(m, finalizer) -} diff --git a/api/v1alpha2/workspace_helpers.go b/api/v1alpha2/workspace_helpers.go index 0679845d..2120ca2c 100644 --- a/api/v1alpha2/workspace_helpers.go +++ b/api/v1alpha2/workspace_helpers.go @@ -3,18 +3,6 @@ package v1alpha2 -import ( - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -func (w *Workspace) NeedToAddFinalizer(finalizer string) bool { - return w.ObjectMeta.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(w, finalizer) -} - -func (w *Workspace) IsDeletionCandidate(finalizer string) bool { - return !w.ObjectMeta.DeletionTimestamp.IsZero() && controllerutil.ContainsFinalizer(w, finalizer) -} - func (w *Workspace) IsCreationCandidate() bool { return w.Status.WorkspaceID == "" } diff --git a/controllers/agentpool_controller.go b/controllers/agentpool_controller.go index 2d828cab..446b668c 100644 --- a/controllers/agentpool_controller.go +++ b/controllers/agentpool_controller.go @@ -78,7 +78,7 @@ func (r *AgentPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } ap.log.Info("Spec Validation", "msg", "spec is valid") - if ap.instance.NeedToAddFinalizer(agentPoolFinalizer) { + if needToAddFinalizer(&ap.instance, agentPoolFinalizer) { err := r.addFinalizer(ctx, &ap.instance) if err != nil { ap.log.Error(err, "Agent Pool Controller", "msg", fmt.Sprintf("failed to add finalizer %s to the object", agentPoolFinalizer)) @@ -284,7 +284,7 @@ func (r *AgentPoolReconciler) reconcileAgentPool(ctx context.Context, ap *agentP var agentPool *tfc.AgentPool var err error - if ap.instance.IsDeletionCandidate(agentPoolFinalizer) { + if isDeletionCandidate(&ap.instance, agentPoolFinalizer) { ap.log.Info("Reconcile Agent Pool", "msg", "object marked as deleted, need to delete agent pool first") r.Recorder.Event(&ap.instance, corev1.EventTypeNormal, "ReconcileAgentPool", "Object marked as deleted, need to delete agent pool first") return r.deleteAgentPool(ctx, ap) diff --git a/controllers/helpers.go b/controllers/helpers.go index 8ec56701..6ca46aac 100644 --- a/controllers/helpers.go +++ b/controllers/helpers.go @@ -10,6 +10,8 @@ import ( "time" tfc "github.com/hashicorp/go-tfe" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -48,3 +50,19 @@ func formatOutput(o *tfc.StateVersionOutput) (string, error) { return string(b), nil } } + +type Object interface { + client.Object +} + +// needToAddFinalizer reports true when a given object doesn't contain a given finalizer and it is not marked for deletion. +// Otherwise, it reports false. +func needToAddFinalizer[T Object](o T, finalizer string) bool { + return o.GetDeletionTimestamp().IsZero() && !controllerutil.ContainsFinalizer(o, finalizer) +} + +// isDeletionCandidate reports true when a given object contains a given finalizer and it is marked for deletion. +// Otherwise, it reports false. +func isDeletionCandidate[T Object](o T, finalizer string) bool { + return !o.GetDeletionTimestamp().IsZero() && controllerutil.ContainsFinalizer(o, finalizer) +} diff --git a/controllers/helpers_test.go b/controllers/helpers_test.go index d2dc24ae..4a2ff8eb 100644 --- a/controllers/helpers_test.go +++ b/controllers/helpers_test.go @@ -10,11 +10,21 @@ import ( tfc "github.com/hashicorp/go-tfe" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -var _ = Describe("Helpers", func() { +type TestObject struct { + metav1.TypeMeta + metav1.ObjectMeta +} + +func (in *TestObject) DeepCopyObject() runtime.Object { + return nil +} + +var _ = Describe("Helpers", Label("Unit"), func() { Context("Returns", func() { It("do not requeue", func() { result, err := doNotRequeue() @@ -102,4 +112,54 @@ var _ = Describe("Helpers", func() { Expect(err).To(BeNil()) }) }) + + Context("NeedToAddFinalizer", func() { + testFinalizer := "test.app.terraform.io/finalizer" + o := TestObject{} + It("No deletion timestamp and no finalizer", func() { + o.ObjectMeta.DeletionTimestamp = nil + o.ObjectMeta.Finalizers = []string{} + Expect(needToAddFinalizer(&o, testFinalizer)).To(BeTrue()) + }) + It("No deletion timestamp and finalizer", func() { + o.ObjectMeta.DeletionTimestamp = nil + o.ObjectMeta.Finalizers = []string{testFinalizer} + Expect(needToAddFinalizer(&o, testFinalizer)).To(BeFalse()) + }) + It("Deletion timestamp and no finalizer", func() { + o.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()} + o.ObjectMeta.Finalizers = []string{} + Expect(needToAddFinalizer(&o, testFinalizer)).To(BeFalse()) + }) + It("Deletion timestamp and finalizer", func() { + o.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()} + o.ObjectMeta.Finalizers = []string{testFinalizer} + Expect(needToAddFinalizer(&o, testFinalizer)).To(BeFalse()) + }) + }) + + Context("IsDeletionCandidate", func() { + testFinalizer := "test.app.terraform.io/finalizer" + o := TestObject{} + It("No deletion timestamp and no finalizer", func() { + o.ObjectMeta.DeletionTimestamp = nil + o.ObjectMeta.Finalizers = []string{} + Expect(isDeletionCandidate(&o, testFinalizer)).To(BeFalse()) + }) + It("No deletion timestamp and finalizer", func() { + o.ObjectMeta.DeletionTimestamp = nil + o.ObjectMeta.Finalizers = []string{testFinalizer} + Expect(isDeletionCandidate(&o, testFinalizer)).To(BeFalse()) + }) + It("Deletion timestamp and no finalizer", func() { + o.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()} + o.ObjectMeta.Finalizers = []string{} + Expect(isDeletionCandidate(&o, testFinalizer)).To(BeFalse()) + }) + It("Deletion timestamp and finalizer", func() { + o.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()} + o.ObjectMeta.Finalizers = []string{testFinalizer} + Expect(isDeletionCandidate(&o, testFinalizer)).To(BeTrue()) + }) + }) }) diff --git a/controllers/module_controller.go b/controllers/module_controller.go index 0420405b..3eceb575 100644 --- a/controllers/module_controller.go +++ b/controllers/module_controller.go @@ -84,7 +84,7 @@ func (r *ModuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr } m.log.Info("Spec Validation", "msg", "spec is valid") - if m.instance.NeedToAddFinalizer(moduleFinalizer) { + if needToAddFinalizer(&m.instance, moduleFinalizer) { err := r.addFinalizer(ctx, &m.instance) if err != nil { m.log.Error(err, "Module Controller", "msg", fmt.Sprintf("failed to add finalizer %s to the object", moduleFinalizer)) @@ -436,7 +436,7 @@ func (r *ModuleReconciler) reconcileModule(ctx context.Context, m *moduleInstanc m.log.Info("Reconcile Module", "msg", "reconciling module") // verify whether the Kubernetes object has been marked as deleted and if so delete the module - if m.instance.IsDeletionCandidate(moduleFinalizer) { + if isDeletionCandidate(&m.instance, moduleFinalizer) { m.log.Info("Reconcile Module", "msg", "object marked as deleted") r.Recorder.Event(&m.instance, corev1.EventTypeNormal, "ReconcileModule", "Object marked as deleted") return r.deleteModule(ctx, m) diff --git a/controllers/workspace_controller.go b/controllers/workspace_controller.go index 9b263836..f6efe69b 100644 --- a/controllers/workspace_controller.go +++ b/controllers/workspace_controller.go @@ -85,7 +85,7 @@ func (r *WorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } w.log.Info("Spec Validation", "msg", "spec is valid") - if w.instance.NeedToAddFinalizer(workspaceFinalizer) { + if needToAddFinalizer(&w.instance, workspaceFinalizer) { err := r.addFinalizer(ctx, &w.instance) if err != nil { w.log.Error(err, "Workspace Controller", "msg", fmt.Sprintf("failed to add finalizer %s to the object", workspaceFinalizer)) @@ -495,7 +495,7 @@ func (r *WorkspaceReconciler) reconcileWorkspace(ctx context.Context, w *workspa }() // verify whether the Kubernetes object has been marked as deleted and if so delete the workspace - if w.instance.IsDeletionCandidate(workspaceFinalizer) { + if isDeletionCandidate(&w.instance, workspaceFinalizer) { w.log.Info("Reconcile Workspace", "msg", "object marked as deleted, need to delete workspace first") r.Recorder.Event(&w.instance, corev1.EventTypeNormal, "ReconcileWorkspace", "Object marked as deleted, need to delete workspace first") return r.deleteWorkspace(ctx, w)