diff --git a/pkg/controller/controllerutil/controllerutil.go b/pkg/controller/controllerutil/controllerutil.go index f76e012ea8..8503634406 100644 --- a/pkg/controller/controllerutil/controllerutil.go +++ b/pkg/controller/controllerutil/controllerutil.go @@ -91,6 +91,37 @@ func SetControllerReference(owner, controlled metav1.Object, scheme *runtime.Sch return nil } +// RemoveControllerReference is a helper method to make sure the given object removes a controller reference to the object provided. +// This allows you to remove the owner to establish a new owner of the object in a subsequent call. +func RemoveControllerReference(owner, object metav1.Object, scheme *runtime.Scheme) error { + owners := object.GetOwnerReferences() + length := len(owners) + if length < 1 { + return fmt.Errorf("%T does not have any owner references", object) + } + ro, ok := owner.(runtime.Object) + if !ok { + return fmt.Errorf("%T is not a runtime.Object, cannot call RemoveControllerReference", owner) + } + gvk, err := apiutil.GVKForObject(ro, scheme) + if err != nil { + return err + } + + index := indexOwnerRef(owners, metav1.OwnerReference{ + APIVersion: gvk.GroupVersion().String(), + Name: owner.GetName(), + Kind: gvk.Kind, + }) + if index == -1 { + return fmt.Errorf("%T does not have an owner reference for %T", object, owner) + } + + owners = append(owners[:index], owners[index+1:]...) + object.SetOwnerReferences(owners) + return nil +} + // SetOwnerReference is a helper method to make sure the given object contains an object reference to the object provided. // This allows you to declare that owner has a dependency on the object without specifying it as a controller. // If a reference to the same object already exists, it'll be overwritten with the newly provided version. diff --git a/pkg/controller/controllerutil/controllerutil_test.go b/pkg/controller/controllerutil/controllerutil_test.go index a058ce0714..5c481447df 100644 --- a/pkg/controller/controllerutil/controllerutil_test.go +++ b/pkg/controller/controllerutil/controllerutil_test.go @@ -103,7 +103,6 @@ var _ = Describe("Controllerutil", func() { })) }) }) - Describe("SetControllerReference", func() { It("should set the OwnerReference if it can find the group version kind", func() { rs := &appsv1.ReplicaSet{} @@ -256,6 +255,98 @@ var _ = Describe("Controllerutil", func() { })) }) }) + Describe("RemoveControllerReference", func() { + It("should remove the owner reference established by the SetControllerReference function", func() { + rs := &appsv1.ReplicaSet{} + dep := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, + } + + Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) + t := true + Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{ + Name: "foo", + Kind: "Deployment", + APIVersion: "extensions/v1beta1", + UID: "foo-uid", + Controller: &t, + BlockOwnerDeletion: &t, + })) + Expect(controllerutil.RemoveControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(len(rs.GetOwnerReferences())).To(BeEquivalentTo(0)) + }) + It("should fail and return an error if the length is less than 1", func() { + rs := &appsv1.ReplicaSet{} + dep := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, + } + Expect(controllerutil.RemoveControllerReference(dep, rs, scheme.Scheme)).To(HaveOccurred()) + }) + It("should fail and return an error because the owner doesn't exist to remove", func() { + rs := &appsv1.ReplicaSet{} + dep := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, + } + dep2 := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "bar", UID: "bar-uid"}, + } + Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(controllerutil.RemoveControllerReference(dep2, rs, scheme.Scheme)).To(HaveOccurred()) + }) + It("should only delete the controller reference and not the other owner references", func() { + rs := &appsv1.ReplicaSet{} + dep := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, + } + dep2 := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "bar", UID: "bar-uid"}, + } + Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(controllerutil.SetOwnerReference(dep2, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(len(rs.GetOwnerReferences())).To(BeEquivalentTo(2)) + Expect(controllerutil.RemoveControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(len(rs.GetOwnerReferences())).To(BeEquivalentTo(1)) + }) + It("should only delete the controller reference and not the other owner references in different order", func() { + rs := &appsv1.ReplicaSet{} + dep := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, + } + dep2 := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "bar", UID: "bar-uid"}, + } + Expect(controllerutil.SetOwnerReference(dep2, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(len(rs.GetOwnerReferences())).To(BeEquivalentTo(2)) + Expect(controllerutil.RemoveControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(len(rs.GetOwnerReferences())).To(BeEquivalentTo(1)) + }) + It("should only fail because the scheme is wrong for the object", func() { + rs := &appsv1.ReplicaSet{} + dep := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, + } + dep2 := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "bar", UID: "bar-uid"}, + } + Expect(controllerutil.SetOwnerReference(dep2, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(len(rs.GetOwnerReferences())).To(BeEquivalentTo(2)) + Expect(controllerutil.RemoveControllerReference(dep, rs, runtime.NewScheme())).To(HaveOccurred()) + Expect(len(rs.GetOwnerReferences())).To(BeEquivalentTo(2)) + }) + It("should only fail because the object is not a runtime.Object", func() { + var obj metav1.Object + rs := &appsv1.ReplicaSet{} + dep := &extensionsv1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"}, + } + Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred()) + Expect(len(rs.GetOwnerReferences())).To(BeEquivalentTo(1)) + Expect(controllerutil.RemoveControllerReference(obj, rs, scheme.Scheme)).To(HaveOccurred()) + Expect(len(rs.GetOwnerReferences())).To(BeEquivalentTo(1)) + }) + }) Describe("CreateOrUpdate", func() { var deploy *appsv1.Deployment