From a5feeb69455334ad4b0c25a6da5e13f6d41aabb2 Mon Sep 17 00:00:00 2001 From: Axel Christ Date: Wed, 23 Feb 2022 09:07:53 +0100 Subject: [PATCH] Implement metautils.IsControlledBy --- metautils/metautils.go | 28 ++++++++++++ metautils/metautils_test.go | 89 +++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/metautils/metautils.go b/metautils/metautils.go index 913dd64..b1234f5 100644 --- a/metautils/metautils.go +++ b/metautils/metautils.go @@ -16,12 +16,15 @@ package metautils import ( + "fmt" "reflect" "strings" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) @@ -68,3 +71,28 @@ func ListElementType(list runtime.Object) (reflect.Type, error) { v := reflect.ValueOf(itemsPtr) return v.Type().Elem().Elem(), nil } + +// IsControlledBy checks if controlled is controlled by owner. +// An object is considered to be controlled if there is a controller (via metav1.GetControllerOf) whose +// GVK, name and UID match with the controller object. +func IsControlledBy(scheme *runtime.Scheme, owner, controlled client.Object) (bool, error) { + controller := metav1.GetControllerOf(controlled) + if controller == nil { + return false, nil + } + + gvk, err := apiutil.GVKForObject(owner, scheme) + if err != nil { + return false, fmt.Errorf("error getting object kinds of owner: %w", err) + } + + gv, err := schema.ParseGroupVersion(controller.APIVersion) + if err != nil { + return false, fmt.Errorf("could not parse controller api version: %w", err) + } + + return gvk.GroupVersion() == gv && + controller.Kind == gvk.Kind && + controller.Name == owner.GetName() && + controller.UID == owner.GetUID(), nil +} diff --git a/metautils/metautils_test.go b/metautils/metautils_test.go index 5f61189..951fda1 100644 --- a/metautils/metautils_test.go +++ b/metautils/metautils_test.go @@ -22,9 +22,12 @@ import ( . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) var _ = Describe("Metautils", func() { @@ -81,4 +84,90 @@ var _ = Describe("Metautils", func() { )).To(HaveOccurred()) }) }) + + Describe("IsControlledBy", func() { + It("should report true if the object is controlled by another", func() { + By("making a controlling object") + owner := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + Name: "owner", + UID: types.UID("owner-uuid"), + }, + } + + By("making an object to be controlled") + owned := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + Name: "owned", + UID: types.UID("owned-uuid"), + }, + } + + By("setting the controller reference") + Expect(controllerutil.SetControllerReference(owner, owned, scheme.Scheme)).To(Succeed()) + + By("asserting the object reports as controlled") + Expect(IsControlledBy(scheme.Scheme, owner, owned)).To(BeTrue(), "object should be controlled by owner, object: %#v, owner: %#v", owned, owner) + }) + + It("should report false if the object is not controlled by another", func() { + By("making two regular objects") + obj1 := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + Name: "obj1", + UID: types.UID("obj1-uuid"), + }, + } + obj2 := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + Name: "obj2", + UID: types.UID("obj2-uuid"), + }, + } + + By("asserting the object does not report as controlled") + Expect(IsControlledBy(scheme.Scheme, obj1, obj2)).To(BeFalse(), "object should not be controlled, obj1: %#v, obj2: %#v", obj1, obj2) + }) + + It("should error if it cannot determine the gvk of an object", func() { + By("creating an object whose type is not registered in the default scheme") + obj1 := &struct{ corev1.ConfigMap }{ + ConfigMap: corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + Name: "obj1", + UID: types.UID("obj1-uuid"), + }, + }, + } + + By("making a controlling object") + owner := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + Name: "owner", + UID: types.UID("owner-uuid"), + }, + } + + By("making a regular object") + owned := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: corev1.NamespaceDefault, + Name: "owned", + UID: types.UID("owned-uuid"), + }, + } + + By("setting the controller for owned") + Expect(controllerutil.SetControllerReference(owner, owned, scheme.Scheme)).To(Succeed()) + + _, err := IsControlledBy(scheme.Scheme, obj1, owned) + Expect(err).To(HaveOccurred()) + }) + }) })