Skip to content

Commit c783d25

Browse files
committed
Add client.{GroupVersionKindFor, IsObjectNamespaced}
Add commonly used utilities that can be nicely wrapped in the built-in clients, in more details GroupVersionKindFor(obj) can be used to retrieve the GVK for a given object, while IsObjectNamespace can be used to determine if an object is global, or namespace scoped. Signed-off-by: Vince Prignano <vincepri@redhat.com>
1 parent 9241bce commit c783d25

File tree

9 files changed

+104
-54
lines changed

9 files changed

+104
-54
lines changed

pkg/cache/multi_namespace_cache.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"k8s.io/client-go/rest"
2929
toolscache "k8s.io/client-go/tools/cache"
3030
"sigs.k8s.io/controller-runtime/pkg/client"
31-
"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
31+
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
3232
)
3333

3434
// NewCacheFunc - Function for creating a new cache from the options and a rest config.
@@ -89,7 +89,7 @@ func (c *multiNamespaceCache) GetInformer(ctx context.Context, obj client.Object
8989

9090
// If the object is clusterscoped, get the informer from clusterCache,
9191
// if not use the namespaced caches.
92-
isNamespaced, err := objectutil.IsAPINamespaced(obj, c.Scheme, c.RESTMapper)
92+
isNamespaced, err := apiutil.IsObjectNamespaced(obj, c.Scheme, c.RESTMapper)
9393
if err != nil {
9494
return nil, err
9595
}
@@ -119,7 +119,7 @@ func (c *multiNamespaceCache) GetInformerForKind(ctx context.Context, gvk schema
119119

120120
// If the object is clusterscoped, get the informer from clusterCache,
121121
// if not use the namespaced caches.
122-
isNamespaced, err := objectutil.IsAPINamespacedWithGVK(gvk, c.Scheme, c.RESTMapper)
122+
isNamespaced, err := apiutil.IsGVKNamespaced(gvk, c.RESTMapper)
123123
if err != nil {
124124
return nil, err
125125
}
@@ -183,7 +183,7 @@ func (c *multiNamespaceCache) WaitForCacheSync(ctx context.Context) bool {
183183
}
184184

185185
func (c *multiNamespaceCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
186-
isNamespaced, err := objectutil.IsAPINamespaced(obj, c.Scheme, c.RESTMapper)
186+
isNamespaced, err := apiutil.IsObjectNamespaced(obj, c.Scheme, c.RESTMapper)
187187
if err != nil {
188188
return nil //nolint:nilerr
189189
}
@@ -201,7 +201,7 @@ func (c *multiNamespaceCache) IndexField(ctx context.Context, obj client.Object,
201201
}
202202

203203
func (c *multiNamespaceCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
204-
isNamespaced, err := objectutil.IsAPINamespaced(obj, c.Scheme, c.RESTMapper)
204+
isNamespaced, err := apiutil.IsObjectNamespaced(obj, c.Scheme, c.RESTMapper)
205205
if err != nil {
206206
return err
207207
}
@@ -223,7 +223,7 @@ func (c *multiNamespaceCache) List(ctx context.Context, list client.ObjectList,
223223
listOpts := client.ListOptions{}
224224
listOpts.ApplyOptions(opts)
225225

226-
isNamespaced, err := objectutil.IsAPINamespaced(list, c.Scheme, c.RESTMapper)
226+
isNamespaced, err := apiutil.IsObjectNamespaced(list, c.Scheme, c.RESTMapper)
227227
if err != nil {
228228
return err
229229
}

pkg/client/apiutil/apimachinery.go

+31
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ limitations under the License.
2020
package apiutil
2121

2222
import (
23+
"errors"
2324
"fmt"
2425
"reflect"
2526
"sync"
@@ -72,6 +73,36 @@ func NewDiscoveryRESTMapper(c *rest.Config) (meta.RESTMapper, error) {
7273
return restmapper.NewDiscoveryRESTMapper(gr), nil
7374
}
7475

76+
// IsObjectNamespaced returns true if the object is namespace scoped.
77+
// For unstructured objects the gvk is found from the object itself.
78+
func IsObjectNamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper meta.RESTMapper) (bool, error) {
79+
gvk, err := GVKForObject(obj, scheme)
80+
if err != nil {
81+
return false, err
82+
}
83+
84+
return IsGVKNamespaced(gvk, restmapper)
85+
}
86+
87+
// IsGVKNamespaced returns true if the object having the provided
88+
// GVK is namespace scoped.
89+
func IsGVKNamespaced(gvk schema.GroupVersionKind, restmapper meta.RESTMapper) (bool, error) {
90+
restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind})
91+
if err != nil {
92+
return false, fmt.Errorf("failed to get restmapping: %w", err)
93+
}
94+
95+
scope := restmapping.Scope.Name()
96+
if scope == "" {
97+
return false, errors.New("scope cannot be identified, empty scope returned")
98+
}
99+
100+
if scope != meta.RESTScopeNameRoot {
101+
return true, nil
102+
}
103+
return false, nil
104+
}
105+
75106
// GVKForObject finds the GroupVersionKind associated with the given object, if there is only a single such GVK.
76107
func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
77108
// TODO(directxman12): do we want to generalize this to arbitrary container types?

pkg/client/client.go

+10
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ func (c *client) resetGroupVersionKind(obj runtime.Object, gvk schema.GroupVersi
168168
}
169169
}
170170

171+
// GroupVersionKindFor returns the GroupVersionKind for the given object.
172+
func (c *client) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
173+
return apiutil.GVKForObject(obj, c.scheme)
174+
}
175+
176+
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
177+
func (c *client) IsObjectNamespaced(obj Object) (bool, error) {
178+
return apiutil.IsObjectNamespaced(obj, c.scheme, c.mapper)
179+
}
180+
171181
// Scheme returns the scheme this client is using.
172182
func (c *client) Scheme() *runtime.Scheme {
173183
return c.scheme

pkg/client/dryrun.go

+11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"k8s.io/apimachinery/pkg/api/meta"
2323
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/runtime/schema"
2425
)
2526

2627
// NewDryRunClient wraps an existing client and enforces DryRun mode
@@ -46,6 +47,16 @@ func (c *dryRunClient) RESTMapper() meta.RESTMapper {
4647
return c.client.RESTMapper()
4748
}
4849

50+
// GroupVersionKindFor returns the GroupVersionKind for the given object.
51+
func (c *dryRunClient) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
52+
return c.client.GroupVersionKindFor(obj)
53+
}
54+
55+
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
56+
func (c *dryRunClient) IsObjectNamespaced(obj Object) (bool, error) {
57+
return c.client.IsObjectNamespaced(obj)
58+
}
59+
4960
// Create implements client.Client.
5061
func (c *dryRunClient) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
5162
return c.client.Create(ctx, obj, append(opts, DryRunAll)...)

pkg/client/fake/client.go

+10
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,16 @@ func (c *fakeClient) RESTMapper() meta.RESTMapper {
563563
return c.restMapper
564564
}
565565

566+
// GroupVersionKindFor returns the GroupVersionKind for the given object.
567+
func (c *fakeClient) GroupVersionKindFor(obj client.Object) (schema.GroupVersionKind, error) {
568+
return apiutil.GVKForObject(obj, c.scheme)
569+
}
570+
571+
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
572+
func (c *fakeClient) IsObjectNamespaced(obj client.Object) (bool, error) {
573+
return apiutil.IsObjectNamespaced(obj, c.scheme, c.restMapper)
574+
}
575+
566576
func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
567577
createOptions := &client.CreateOptions{}
568578
createOptions.ApplyOptions(opts)

pkg/client/interfaces.go

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121

2222
apierrors "k8s.io/apimachinery/pkg/api/errors"
23+
"k8s.io/apimachinery/pkg/runtime/schema"
2324

2425
"k8s.io/apimachinery/pkg/api/meta"
2526
"k8s.io/apimachinery/pkg/runtime"
@@ -169,6 +170,10 @@ type Client interface {
169170
Scheme() *runtime.Scheme
170171
// RESTMapper returns the rest this client is using.
171172
RESTMapper() meta.RESTMapper
173+
// GroupVersionKindFor returns the GroupVersionKind for the given object.
174+
GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error)
175+
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
176+
IsObjectNamespaced(obj Object) (bool, error)
172177
}
173178

174179
// WithWatch supports Watch on top of the CRUD operations supported by

pkg/client/namespaced_client.go

+21-12
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222

2323
"k8s.io/apimachinery/pkg/api/meta"
2424
"k8s.io/apimachinery/pkg/runtime"
25-
"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
25+
"k8s.io/apimachinery/pkg/runtime/schema"
2626
)
2727

2828
// NewNamespacedClient wraps an existing client enforcing the namespace value.
@@ -52,9 +52,19 @@ func (n *namespacedClient) RESTMapper() meta.RESTMapper {
5252
return n.client.RESTMapper()
5353
}
5454

55+
// GroupVersionKindFor returns the GroupVersionKind for the given object.
56+
func (n *namespacedClient) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
57+
return n.client.GroupVersionKindFor(obj)
58+
}
59+
60+
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
61+
func (n *namespacedClient) IsObjectNamespaced(obj Object) (bool, error) {
62+
return n.client.IsObjectNamespaced(obj)
63+
}
64+
5565
// Create implements client.Client.
5666
func (n *namespacedClient) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
57-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
67+
isNamespaceScoped, err := n.IsObjectNamespaced(obj)
5868
if err != nil {
5969
return fmt.Errorf("error finding the scope of the object: %w", err)
6070
}
@@ -72,7 +82,7 @@ func (n *namespacedClient) Create(ctx context.Context, obj Object, opts ...Creat
7282

7383
// Update implements client.Client.
7484
func (n *namespacedClient) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
75-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
85+
isNamespaceScoped, err := n.IsObjectNamespaced(obj)
7686
if err != nil {
7787
return fmt.Errorf("error finding the scope of the object: %w", err)
7888
}
@@ -90,7 +100,7 @@ func (n *namespacedClient) Update(ctx context.Context, obj Object, opts ...Updat
90100

91101
// Delete implements client.Client.
92102
func (n *namespacedClient) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
93-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
103+
isNamespaceScoped, err := n.IsObjectNamespaced(obj)
94104
if err != nil {
95105
return fmt.Errorf("error finding the scope of the object: %w", err)
96106
}
@@ -108,7 +118,7 @@ func (n *namespacedClient) Delete(ctx context.Context, obj Object, opts ...Delet
108118

109119
// DeleteAllOf implements client.Client.
110120
func (n *namespacedClient) DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error {
111-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
121+
isNamespaceScoped, err := n.IsObjectNamespaced(obj)
112122
if err != nil {
113123
return fmt.Errorf("error finding the scope of the object: %w", err)
114124
}
@@ -121,7 +131,7 @@ func (n *namespacedClient) DeleteAllOf(ctx context.Context, obj Object, opts ...
121131

122132
// Patch implements client.Client.
123133
func (n *namespacedClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
124-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
134+
isNamespaceScoped, err := n.IsObjectNamespaced(obj)
125135
if err != nil {
126136
return fmt.Errorf("error finding the scope of the object: %w", err)
127137
}
@@ -139,7 +149,7 @@ func (n *namespacedClient) Patch(ctx context.Context, obj Object, patch Patch, o
139149

140150
// Get implements client.Client.
141151
func (n *namespacedClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
142-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
152+
isNamespaceScoped, err := n.IsObjectNamespaced(obj)
143153
if err != nil {
144154
return fmt.Errorf("error finding the scope of the object: %w", err)
145155
}
@@ -180,7 +190,7 @@ type namespacedClientSubResourceClient struct {
180190
}
181191

182192
func (nsw *namespacedClientSubResourceClient) Get(ctx context.Context, obj, subResource Object, opts ...SubResourceGetOption) error {
183-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
193+
isNamespaceScoped, err := nsw.namespacedclient.IsObjectNamespaced(obj)
184194
if err != nil {
185195
return fmt.Errorf("error finding the scope of the object: %w", err)
186196
}
@@ -198,7 +208,7 @@ func (nsw *namespacedClientSubResourceClient) Get(ctx context.Context, obj, subR
198208
}
199209

200210
func (nsw *namespacedClientSubResourceClient) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error {
201-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
211+
isNamespaceScoped, err := nsw.namespacedclient.IsObjectNamespaced(obj)
202212
if err != nil {
203213
return fmt.Errorf("error finding the scope of the object: %w", err)
204214
}
@@ -217,7 +227,7 @@ func (nsw *namespacedClientSubResourceClient) Create(ctx context.Context, obj, s
217227

218228
// Update implements client.SubResourceWriter.
219229
func (nsw *namespacedClientSubResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
220-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
230+
isNamespaceScoped, err := nsw.namespacedclient.IsObjectNamespaced(obj)
221231
if err != nil {
222232
return fmt.Errorf("error finding the scope of the object: %w", err)
223233
}
@@ -235,8 +245,7 @@ func (nsw *namespacedClientSubResourceClient) Update(ctx context.Context, obj Ob
235245

236246
// Patch implements client.SubResourceWriter.
237247
func (nsw *namespacedClientSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
238-
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
239-
248+
isNamespaceScoped, err := nsw.namespacedclient.IsObjectNamespaced(obj)
240249
if err != nil {
241250
return fmt.Errorf("error finding the scope of the object: %w", err)
242251
}

pkg/client/split.go

+10
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ func (d *delegatingClient) RESTMapper() meta.RESTMapper {
8787
return d.mapper
8888
}
8989

90+
// GroupVersionKindFor returns the GroupVersionKind for the given object.
91+
func (d *delegatingClient) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
92+
return apiutil.GVKForObject(obj, d.scheme)
93+
}
94+
95+
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
96+
func (d *delegatingClient) IsObjectNamespaced(obj Object) (bool, error) {
97+
return apiutil.IsObjectNamespaced(obj, d.scheme, d.mapper)
98+
}
99+
90100
// delegatingReader forms a Reader that will cause Get and List requests for
91101
// unstructured types to use the ClientReader while requests for any other type
92102
// of object with use the CacheReader. This avoids accidentally caching the

pkg/internal/objectutil/objectutil.go

-36
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,9 @@ limitations under the License.
1717
package objectutil
1818

1919
import (
20-
"errors"
21-
"fmt"
22-
2320
apimeta "k8s.io/apimachinery/pkg/api/meta"
2421
"k8s.io/apimachinery/pkg/labels"
2522
"k8s.io/apimachinery/pkg/runtime"
26-
"k8s.io/apimachinery/pkg/runtime/schema"
27-
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2823
)
2924

3025
// FilterWithLabels returns a copy of the items in objs matching labelSel.
@@ -45,34 +40,3 @@ func FilterWithLabels(objs []runtime.Object, labelSel labels.Selector) ([]runtim
4540
}
4641
return outItems, nil
4742
}
48-
49-
// IsAPINamespaced returns true if the object is namespace scoped.
50-
// For unstructured objects the gvk is found from the object itself.
51-
func IsAPINamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) {
52-
gvk, err := apiutil.GVKForObject(obj, scheme)
53-
if err != nil {
54-
return false, err
55-
}
56-
57-
return IsAPINamespacedWithGVK(gvk, scheme, restmapper)
58-
}
59-
60-
// IsAPINamespacedWithGVK returns true if the object having the provided
61-
// GVK is namespace scoped.
62-
func IsAPINamespacedWithGVK(gk schema.GroupVersionKind, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) {
63-
restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gk.Group, Kind: gk.Kind})
64-
if err != nil {
65-
return false, fmt.Errorf("failed to get restmapping: %w", err)
66-
}
67-
68-
scope := restmapping.Scope.Name()
69-
70-
if scope == "" {
71-
return false, errors.New("scope cannot be identified, empty scope returned")
72-
}
73-
74-
if scope != apimeta.RESTScopeNameRoot {
75-
return true, nil
76-
}
77-
return false, nil
78-
}

0 commit comments

Comments
 (0)