/
object_key.go
122 lines (106 loc) · 4.29 KB
/
object_key.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package resource
import (
"fmt"
"log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// ObjectKeyFromObject method wraps client.ObjectKeyFromObject method by additionally checking if passed object is
// a cluster-scoped resource (e.g. CustomResourceDefinition, ClusterRole etc.) and removing the namespace from the
// key since cluster-scoped resources are not namespaced.
func ObjectKeyFromObject(r runtime.Object, di discovery.CachedDiscoveryInterface) (client.ObjectKey, error) {
key, err := client.ObjectKeyFromObject(r)
if err != nil {
return client.ObjectKey{}, fmt.Errorf("failed to get an object key from object %v: %v", r.GetObjectKind(), err)
}
// if the resource is cluster-scoped we need to clear then namespace from the key
isNamespaced, err := IsNamespacedObject(r, di)
if err != nil {
return client.ObjectKey{}, fmt.Errorf("failed to determine if the resource %v is cluster-scoped: %v", r.GetObjectKind(), err)
}
if !isNamespaced {
key.Namespace = ""
}
return key, nil
}
func IsNamespacedObject(r runtime.Object, di discovery.CachedDiscoveryInterface) (bool, error) {
gvk := r.GetObjectKind().GroupVersionKind()
return isNamespaced(gvk, di)
}
func IsKnownObjectType(r runtime.Object, di discovery.CachedDiscoveryInterface) (bool, error) {
gvk := r.GetObjectKind().GroupVersionKind()
return isKnownType(gvk, di)
}
// isNamespaced method return true if given runtime.Object is a namespaced (not cluster-scoped) resource. It uses the
// discovery client to fetch all API resources (with Groups and Versions), searches for a resource with the passed GVK
// and returns true if it's namespaced. Method returns an error if passed GVK wasn't found in the discovered resource list.
func isNamespaced(gvk schema.GroupVersionKind, di discovery.CachedDiscoveryInterface) (bool, error) {
apiResource, err := getUncachedAPIResource(gvk, di)
if err != nil {
return false, err
}
if apiResource != nil {
return apiResource.Namespaced, nil
}
return false, fmt.Errorf("a resource with GVK %v seems to be missing in API resource list", gvk)
}
func isKnownType(gvk schema.GroupVersionKind, di discovery.CachedDiscoveryInterface) (bool, error) {
apiResource, err := getUncachedAPIResource(gvk, di)
if err != nil {
return false, err
}
if apiResource != nil {
return true, nil
}
return false, nil
}
// getUncachedAPIResource tries to invalidate the cache and requery the discovery interface to make sure no stale data is returned
func getUncachedAPIResource(gvk schema.GroupVersionKind, di discovery.CachedDiscoveryInterface) (*metav1.APIResource, error) {
// First try, this may return nil because of the cache
apiResource, err := getAPIResource(gvk, di)
if err != nil {
return nil, err
}
if apiResource != nil {
return apiResource, nil
}
// Second try, now with invalidated cache. If we still get nil, we know it's not there.
log.Printf("Failed to get APIResource for %v, retry with invalidated cache.", gvk)
di.Invalidate()
apiResource, err = getAPIResource(gvk, di)
if err != nil {
return nil, err
}
if apiResource != nil {
return apiResource, nil
}
return nil, nil
}
// getAPIResource returns a specific APIResource from the DiscoveryInterface or nil if no resource was found.
// As the CachedDiscoverInterface may contain stale data, it can return nil even if the resource actually exists, in that
// case it is advised to invalidate the DI cache and retry the query
// Additionally, this method may return false positives, i.e. an API resource that was already deleted from the api
// server. If no false positive results is required, call di.Invalidate before calling this method
func getAPIResource(gvk schema.GroupVersionKind, di discovery.CachedDiscoveryInterface) (*metav1.APIResource, error) {
resList, err := di.ServerResourcesForGroupVersion(gvk.GroupVersion().String())
if err != nil || resList == nil {
if err == memory.ErrCacheNotFound {
return nil, nil
}
return nil, err
}
gv, err := schema.ParseGroupVersion(resList.GroupVersion)
if err != nil {
return nil, err
}
for _, r := range resList.APIResources {
if gvk == gv.WithKind(r.Kind) {
return &r, nil
}
}
return nil, nil
}