From cbf5989606380d785df62a4ba2bb4c3b6e3dfc69 Mon Sep 17 00:00:00 2001 From: Keegan Witt Date: Sat, 19 Oct 2024 10:24:22 -0400 Subject: [PATCH] Add public functions to add/remove OwnerReferences without the referenced object --- .../controllerutil/controllerutil.go | 30 +++ .../controllerutil/controllerutil_test.go | 207 ++++++++++++++++++ 2 files changed, 237 insertions(+) diff --git a/pkg/controller/controllerutil/controllerutil.go b/pkg/controller/controllerutil/controllerutil.go index ba3f931e47..b5a07fbbbc 100644 --- a/pkg/controller/controllerutil/controllerutil.go +++ b/pkg/controller/controllerutil/controllerutil.go @@ -168,6 +168,36 @@ func RemoveOwnerReference(owner, object metav1.Object, scheme *runtime.Scheme) e return nil } +// AppendOwnerReference is a helper method to make sure the given object contains a provided owner reference. +// This allows you to declare that owner has a dependency on the object without specifying it as a controller. +// If a reference already exists, it'll be overwritten with the newly provided version. +func AppendOwnerReference(owner metav1.OwnerReference, object metav1.Object) { + upsertOwnerRef(owner, object) +} + +// DropOwnerReference is a helper method to make sure the given object removes a provided owner reference. +// This allows you to remove the owner to establish a new owner of the object in a subsequent call. +func DropOwnerReference(owner metav1.OwnerReference, object metav1.Object) error { + ownerRefs := object.GetOwnerReferences() + index := indexOwnerRef(ownerRefs, metav1.OwnerReference{ + APIVersion: owner.APIVersion, + Name: owner.Name, + Kind: owner.Kind, + }) + + if index == -1 { + return fmt.Errorf("%T does not have an controller reference for %T", object, owner) + } + + if ownerRefs[index].Controller == nil || !*ownerRefs[index].Controller { + return fmt.Errorf("%T owner is not the controller reference for %T", owner, object) + } + + ownerRefs = append(ownerRefs[:index], ownerRefs[index+1:]...) + object.SetOwnerReferences(ownerRefs) + return nil +} + // HasControllerReference returns true if the object // has an owner ref with controller equal to true func HasControllerReference(object metav1.Object) bool { diff --git a/pkg/controller/controllerutil/controllerutil_test.go b/pkg/controller/controllerutil/controllerutil_test.go index c275d3d2dd..8be5c6c0ec 100644 --- a/pkg/controller/controllerutil/controllerutil_test.go +++ b/pkg/controller/controllerutil/controllerutil_test.go @@ -276,6 +276,213 @@ var _ = Describe("Controllerutil", func() { }) }) + var _ = Describe("Controllerutil", func() { + Describe("AppendOwnerReference", func() { + It("should set ownerRef on an empty list", func() { + rs := &appsv1.ReplicaSet{} + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid", + } + controllerutil.AppendOwnerReference(dep, rs) + Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ + Name: "foo", + Kind: "Deployment", + APIVersion: "extensions/v1beta1", + UID: "foo-uid", + })) + }) + + It("should not duplicate owner references", func() { + rs := &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + Name: "foo", + Kind: "Deployment", + APIVersion: "extensions/v1beta1", + UID: "foo-uid", + }, + }, + }, + } + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid", + } + + controllerutil.AppendOwnerReference(dep, rs) + Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ + Name: "foo", + Kind: "Deployment", + APIVersion: "extensions/v1beta1", + UID: "foo-uid", + })) + }) + + It("should update the reference", func() { + rs := &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + Name: "foo", + Kind: "Deployment", + APIVersion: "extensions/v1alpha1", + UID: "foo-uid-1", + }, + }, + }, + } + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid-2", + } + + controllerutil.AppendOwnerReference(dep, rs) + Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ + Name: "foo", + Kind: "Deployment", + APIVersion: "extensions/v1beta1", + UID: "foo-uid-2", + })) + }) + It("should remove the owner reference", func() { + rs := &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + Name: "foo", + Kind: "Deployment", + APIVersion: "extensions/v1alpha1", + UID: "foo-uid-1", + }, + }, + }, + } + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid-2", + } + + controllerutil.AppendOwnerReference(dep, rs) + Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ + Name: "foo", + Kind: "Deployment", + APIVersion: "extensions/v1beta1", + UID: "foo-uid-2", + })) + Expect(controllerutil.DropOwnerReference(dep, rs)).ToNot(HaveOccurred()) + Expect(rs.GetOwnerReferences()).To(BeEmpty()) + }) + It("should error when trying to remove the reference that doesn't exist", func() { + rs := &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{}, + } + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid-2", + } + Expect(controllerutil.DropOwnerReference(dep, rs)).To(HaveOccurred()) + Expect(rs.GetOwnerReferences()).To(BeEmpty()) + }) + It("should error when trying to remove the reference that doesn't abide by the scheme", func() { + rs := &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{}, + } + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid-2", + } + controllerutil.AppendOwnerReference(dep, rs) + Expect(controllerutil.DropOwnerReference(dep, rs)).To(HaveOccurred()) + Expect(rs.GetOwnerReferences()).To(HaveLen(1)) + }) + It("should error when trying to remove the owner when setting the owner as a non runtime.Object", func() { + rs := &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{}, + } + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid-2", + } + controllerutil.AppendOwnerReference(dep, rs) + Expect(controllerutil.DropOwnerReference(dep, rs)).To(HaveOccurred()) + Expect(rs.GetOwnerReferences()).To(HaveLen(1)) + }) + + It("should error when trying to remove an owner that doesn't exist", func() { + rs := &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{}, + } + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid-2", + } + dep2 := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid-3", + } + controllerutil.AppendOwnerReference(dep, rs) + Expect(controllerutil.DropOwnerReference(dep2, rs)).To(HaveOccurred()) + Expect(rs.GetOwnerReferences()).To(HaveLen(1)) + }) + + It("should error when DropOwnerReference owner's controller is set to false", func() { + rs := &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{}, + } + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid-2", + } + controllerutil.AppendOwnerReference(dep, rs) + Expect(controllerutil.DropOwnerReference(dep, rs)).To(HaveOccurred()) + }) + + It("should error when DropOwnerReference passed in owner is not the owner", func() { + rs := &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{}, + } + dep := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo", + UID: "foo-uid-2", + } + dep2 := metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Deployment", + Name: "foo-2", + UID: "foo-uid-42", + } + controllerutil.AppendOwnerReference(dep, rs) + controllerutil.AppendOwnerReference(dep2, rs) + Expect(controllerutil.DropOwnerReference(dep2, rs)).To(HaveOccurred()) + Expect(rs.GetOwnerReferences()).To(HaveLen(2)) + }) + }) + }) + Describe("SetControllerReference", func() { It("should set the OwnerReference if it can find the group version kind", func() { rs := &appsv1.ReplicaSet{}