Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚠️ Add client.{GroupVersionKindFor, IsObjectNamespaced} #2136

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions pkg/cache/multi_namespace_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"k8s.io/client-go/rest"
toolscache "k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)

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

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

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

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

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

isNamespaced, err := objectutil.IsAPINamespaced(list, c.Scheme, c.RESTMapper)
isNamespaced, err := apiutil.IsObjectNamespaced(list, c.Scheme, c.RESTMapper)
if err != nil {
return err
}
Expand Down
31 changes: 31 additions & 0 deletions pkg/client/apiutil/apimachinery.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ limitations under the License.
package apiutil

import (
"errors"
"fmt"
"reflect"
"sync"
Expand Down Expand Up @@ -72,6 +73,36 @@ func NewDiscoveryRESTMapper(c *rest.Config) (meta.RESTMapper, error) {
return restmapper.NewDiscoveryRESTMapper(gr), nil
}

// IsObjectNamespaced returns true if the object is namespace scoped.
// For unstructured objects the gvk is found from the object itself.
func IsObjectNamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper meta.RESTMapper) (bool, error) {
gvk, err := GVKForObject(obj, scheme)
if err != nil {
return false, err
}

return IsGVKNamespaced(gvk, restmapper)
}

// IsGVKNamespaced returns true if the object having the provided
// GVK is namespace scoped.
func IsGVKNamespaced(gvk schema.GroupVersionKind, restmapper meta.RESTMapper) (bool, error) {
restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind})
if err != nil {
return false, fmt.Errorf("failed to get restmapping: %w", err)
}

scope := restmapping.Scope.Name()
if scope == "" {
return false, errors.New("scope cannot be identified, empty scope returned")
}

if scope != meta.RESTScopeNameRoot {
return true, nil
}
return false, nil
}

// GVKForObject finds the GroupVersionKind associated with the given object, if there is only a single such GVK.
func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
// TODO(directxman12): do we want to generalize this to arbitrary container types?
Expand Down
10 changes: 10 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ func (c *client) resetGroupVersionKind(obj runtime.Object, gvk schema.GroupVersi
}
}

// GroupVersionKindFor returns the GroupVersionKind for the given object.
func (c *client) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
return apiutil.GVKForObject(obj, c.scheme)
}

// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
func (c *client) IsObjectNamespaced(obj Object) (bool, error) {
return apiutil.IsObjectNamespaced(obj, c.scheme, c.mapper)
}

// Scheme returns the scheme this client is using.
func (c *client) Scheme() *runtime.Scheme {
return c.scheme
Expand Down
11 changes: 11 additions & 0 deletions pkg/client/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

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

// GroupVersionKindFor returns the GroupVersionKind for the given object.
func (c *dryRunClient) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
return c.client.GroupVersionKindFor(obj)
}

// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
func (c *dryRunClient) IsObjectNamespaced(obj Object) (bool, error) {
return c.client.IsObjectNamespaced(obj)
}

// Create implements client.Client.
func (c *dryRunClient) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
return c.client.Create(ctx, obj, append(opts, DryRunAll)...)
Expand Down
10 changes: 10 additions & 0 deletions pkg/client/fake/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,16 @@ func (c *fakeClient) RESTMapper() meta.RESTMapper {
return c.restMapper
}

// GroupVersionKindFor returns the GroupVersionKind for the given object.
func (c *fakeClient) GroupVersionKindFor(obj client.Object) (schema.GroupVersionKind, error) {
return apiutil.GVKForObject(obj, c.scheme)
}

// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
func (c *fakeClient) IsObjectNamespaced(obj client.Object) (bool, error) {
return apiutil.IsObjectNamespaced(obj, c.scheme, c.restMapper)
}

func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
createOptions := &client.CreateOptions{}
createOptions.ApplyOptions(opts)
Expand Down
5 changes: 5 additions & 0 deletions pkg/client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -169,6 +170,10 @@ type Client interface {
Scheme() *runtime.Scheme
// RESTMapper returns the rest this client is using.
RESTMapper() meta.RESTMapper
// GroupVersionKindFor returns the GroupVersionKind for the given object.
GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error)
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
IsObjectNamespaced(obj Object) (bool, error)
}

// WithWatch supports Watch on top of the CRUD operations supported by
Expand Down
33 changes: 21 additions & 12 deletions pkg/client/namespaced_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
"k8s.io/apimachinery/pkg/runtime/schema"
)

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

// GroupVersionKindFor returns the GroupVersionKind for the given object.
func (n *namespacedClient) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
return n.client.GroupVersionKindFor(obj)
}

// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
func (n *namespacedClient) IsObjectNamespaced(obj Object) (bool, error) {
return n.client.IsObjectNamespaced(obj)
}

// Create implements client.Client.
func (n *namespacedClient) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
isNamespaceScoped, err := n.IsObjectNamespaced(obj)
if err != nil {
return fmt.Errorf("error finding the scope of the object: %w", err)
}
Expand All @@ -72,7 +82,7 @@ func (n *namespacedClient) Create(ctx context.Context, obj Object, opts ...Creat

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

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

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

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

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

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

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

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

// Patch implements client.SubResourceWriter.
func (nsw *namespacedClientSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())

isNamespaceScoped, err := nsw.namespacedclient.IsObjectNamespaced(obj)
if err != nil {
return fmt.Errorf("error finding the scope of the object: %w", err)
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/client/split.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ func (d *delegatingClient) RESTMapper() meta.RESTMapper {
return d.mapper
}

// GroupVersionKindFor returns the GroupVersionKind for the given object.
func (d *delegatingClient) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
return apiutil.GVKForObject(obj, d.scheme)
}

// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
func (d *delegatingClient) IsObjectNamespaced(obj Object) (bool, error) {
return apiutil.IsObjectNamespaced(obj, d.scheme, d.mapper)
}

// delegatingReader forms a Reader that will cause Get and List requests for
// unstructured types to use the ClientReader while requests for any other type
// of object with use the CacheReader. This avoids accidentally caching the
Expand Down
36 changes: 0 additions & 36 deletions pkg/internal/objectutil/objectutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,9 @@ limitations under the License.
package objectutil

import (
"errors"
"fmt"

apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)

// FilterWithLabels returns a copy of the items in objs matching labelSel.
Expand All @@ -45,34 +40,3 @@ func FilterWithLabels(objs []runtime.Object, labelSel labels.Selector) ([]runtim
}
return outItems, nil
}

// IsAPINamespaced returns true if the object is namespace scoped.
// For unstructured objects the gvk is found from the object itself.
func IsAPINamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) {
gvk, err := apiutil.GVKForObject(obj, scheme)
if err != nil {
return false, err
}

return IsAPINamespacedWithGVK(gvk, scheme, restmapper)
}

// IsAPINamespacedWithGVK returns true if the object having the provided
// GVK is namespace scoped.
func IsAPINamespacedWithGVK(gk schema.GroupVersionKind, scheme *runtime.Scheme, restmapper apimeta.RESTMapper) (bool, error) {
restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gk.Group, Kind: gk.Kind})
if err != nil {
return false, fmt.Errorf("failed to get restmapping: %w", err)
}

scope := restmapping.Scope.Name()

if scope == "" {
return false, errors.New("scope cannot be identified, empty scope returned")
}

if scope != apimeta.RESTScopeNameRoot {
return true, nil
}
return false, nil
}