Add a lazy discovery interface for Unstructured

Delays the error until the first call and then preserves it for others.
More closely matches the intent of the Object() calls. Loaders are now
lazy and don't need to return errors directly.

Sets the stage for collapsing unstructured and structured builders
together.
This commit is contained in:
Clayton Coleman
2017-11-14 22:16:10 -05:00
parent 287a3ed1e1
commit e298aa39c3
12 changed files with 202 additions and 71 deletions

View File

@@ -285,14 +285,14 @@ func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
return legacyscheme.Registry.RESTMapper(), f.tf.Typer return legacyscheme.Registry.RESTMapper(), f.tf.Typer
} }
func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) { func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper) {
groupResources := testDynamicResources() groupResources := testDynamicResources()
mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructuredConversion(legacyscheme.Registry.InterfacesFor)) mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructuredConversion(legacyscheme.Registry.InterfacesFor))
typer := discovery.NewUnstructuredObjectTyper(groupResources) typer := discovery.NewUnstructuredObjectTyper(groupResources)
fakeDs := &fakeCachedDiscoveryClient{} fakeDs := &fakeCachedDiscoveryClient{}
expander, err := cmdutil.NewShortcutExpander(mapper, fakeDs) expander := cmdutil.NewShortcutExpander(mapper, fakeDs)
return expander, typer, err return expander, typer
} }
func (f *FakeFactory) CategoryExpander() categories.CategoryExpander { func (f *FakeFactory) CategoryExpander() categories.CategoryExpander {
@@ -519,7 +519,7 @@ func (f *FakeFactory) PrinterForMapping(cmd *cobra.Command, isLocal bool, output
func (f *FakeFactory) NewBuilder() *resource.Builder { func (f *FakeFactory) NewBuilder() *resource.Builder {
mapper, typer := f.Object() mapper, typer := f.Object()
unstructuredMapper, unstructuredTyper, _ := f.UnstructuredObject() unstructuredMapper, unstructuredTyper := f.UnstructuredObject()
return resource.NewBuilder( return resource.NewBuilder(
&resource.Mapper{ &resource.Mapper{
@@ -608,7 +608,7 @@ func (f *fakeAPIFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
return testapi.Default.RESTMapper(), legacyscheme.Scheme return testapi.Default.RESTMapper(), legacyscheme.Scheme
} }
func (f *fakeAPIFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) { func (f *fakeAPIFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper) {
groupResources := testDynamicResources() groupResources := testDynamicResources()
mapper := discovery.NewRESTMapper( mapper := discovery.NewRESTMapper(
groupResources, groupResources,
@@ -629,8 +629,8 @@ func (f *fakeAPIFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTy
typer := discovery.NewUnstructuredObjectTyper(groupResources) typer := discovery.NewUnstructuredObjectTyper(groupResources)
fakeDs := &fakeCachedDiscoveryClient{} fakeDs := &fakeCachedDiscoveryClient{}
expander, err := cmdutil.NewShortcutExpander(mapper, fakeDs) expander := cmdutil.NewShortcutExpander(mapper, fakeDs)
return expander, typer, err return expander, typer
} }
func (f *fakeAPIFactory) Decoder(bool) runtime.Decoder { func (f *fakeAPIFactory) Decoder(bool) runtime.Decoder {
@@ -865,7 +865,7 @@ func (f *fakeAPIFactory) PrinterForMapping(cmd *cobra.Command, isLocal bool, out
func (f *fakeAPIFactory) NewBuilder() *resource.Builder { func (f *fakeAPIFactory) NewBuilder() *resource.Builder {
mapper, typer := f.Object() mapper, typer := f.Object()
unstructuredMapper, unstructuredTyper, _ := f.UnstructuredObject() unstructuredMapper, unstructuredTyper := f.UnstructuredObject()
return resource.NewBuilder( return resource.NewBuilder(
&resource.Mapper{ &resource.Mapper{

View File

@@ -9,6 +9,7 @@ go_library(
srcs = [ srcs = [
"cached_discovery.go", "cached_discovery.go",
"clientcache.go", "clientcache.go",
"discovery.go",
"factory.go", "factory.go",
"factory_builder.go", "factory_builder.go",
"factory_client_access.go", "factory_client_access.go",

View File

@@ -191,7 +191,7 @@ type ObjectMappingFactory interface {
Object() (meta.RESTMapper, runtime.ObjectTyper) Object() (meta.RESTMapper, runtime.ObjectTyper)
// Returns interfaces for dealing with arbitrary // Returns interfaces for dealing with arbitrary
// runtime.Unstructured. This performs API calls to discover types. // runtime.Unstructured. This performs API calls to discover types.
UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper)
// Returns interface for expanding categories like `all`. // Returns interface for expanding categories like `all`.
// TODO: this should probably return an error if the full expander can't be loaded. // TODO: this should probably return an error if the full expander can't be loaded.
CategoryExpander() categories.CategoryExpander CategoryExpander() categories.CategoryExpander

View File

@@ -51,16 +51,12 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac
func (f *ring2Factory) PrinterForCommand(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, options printers.PrintOptions) (printers.ResourcePrinter, error) { func (f *ring2Factory) PrinterForCommand(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, options printers.PrintOptions) (printers.ResourcePrinter, error) {
var mapper meta.RESTMapper var mapper meta.RESTMapper
var typer runtime.ObjectTyper var typer runtime.ObjectTyper
var err error
if isLocal { if isLocal {
mapper = legacyscheme.Registry.RESTMapper() mapper = legacyscheme.Registry.RESTMapper()
typer = legacyscheme.Scheme typer = legacyscheme.Scheme
} else { } else {
mapper, typer, err = f.objectMappingFactory.UnstructuredObject() mapper, typer = f.objectMappingFactory.UnstructuredObject()
if err != nil {
return nil, err
}
} }
// TODO: used by the custom column implementation and the name implementation, break this dependency // TODO: used by the custom column implementation and the name implementation, break this dependency
decoders := []runtime.Decoder{f.clientAccessFactory.Decoder(true), unstructured.UnstructuredJSONScheme} decoders := []runtime.Decoder{f.clientAccessFactory.Decoder(true), unstructured.UnstructuredJSONScheme}
@@ -137,11 +133,7 @@ func (f *ring2Factory) PrintObject(cmd *cobra.Command, isLocal bool, mapper meta
// fall back to an unstructured object if we get something unregistered // fall back to an unstructured object if we get something unregistered
if runtime.IsNotRegisteredError(err) { if runtime.IsNotRegisteredError(err) {
_, typer, unstructuredErr := f.objectMappingFactory.UnstructuredObject() _, typer := f.objectMappingFactory.UnstructuredObject()
if unstructuredErr != nil {
// if we can't get an unstructured typer, return the original error
return err
}
gvks, _, err = typer.ObjectKinds(obj) gvks, _, err = typer.ObjectKinds(obj)
} }
@@ -186,7 +178,7 @@ func (f *ring2Factory) NewBuilder() *resource.Builder {
mapper, typer := f.objectMappingFactory.Object() mapper, typer := f.objectMappingFactory.Object()
unstructuredClientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.UnstructuredClientForMapping) unstructuredClientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.UnstructuredClientForMapping)
unstructuredMapper, unstructuredTyper, _ := f.objectMappingFactory.UnstructuredObject() unstructuredMapper, unstructuredTyper := f.objectMappingFactory.Object()
categoryExpander := f.objectMappingFactory.CategoryExpander() categoryExpander := f.objectMappingFactory.CategoryExpander()

View File

@@ -27,6 +27,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/golang/glog"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -71,34 +73,35 @@ func NewObjectMappingFactory(clientAccessFactory ClientAccessFactory) ObjectMapp
return f return f
} }
// TODO: This method should return an error now that it can fail. Alternatively, it needs to func (f *ring1Factory) objectLoader() (meta.RESTMapper, runtime.ObjectTyper, error) {
// return lazy implementations of mapper and typer that don't hit the wire until they are
// invoked.
func (f *ring1Factory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
mapper := legacyscheme.Registry.RESTMapper() mapper := legacyscheme.Registry.RESTMapper()
discoveryClient, err := f.clientAccessFactory.DiscoveryClient() discoveryClient, err := f.clientAccessFactory.DiscoveryClient()
if err == nil { if err != nil {
mapper = meta.FirstHitRESTMapper{ glog.V(5).Infof("Object loader was unable to retrieve a discovery client: %v", err)
MultiRESTMapper: meta.MultiRESTMapper{ return mapper, legacyscheme.Scheme, nil
discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, legacyscheme.Registry.InterfacesFor), }
legacyscheme.Registry.RESTMapper(), // hardcoded fall back mapper = meta.FirstHitRESTMapper{
}, MultiRESTMapper: meta.MultiRESTMapper{
} discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, legacyscheme.Registry.InterfacesFor),
legacyscheme.Registry.RESTMapper(), // hardcoded fall back
// wrap with shortcuts, they require a discoveryClient },
mapper, err = NewShortcutExpander(mapper, discoveryClient)
// you only have an error on missing discoveryClient, so this shouldn't fail. Check anyway.
CheckErr(err)
} }
return mapper, legacyscheme.Scheme // wrap with shortcuts, they require a discoveryClient
mapper = NewShortcutExpander(mapper, discoveryClient)
return mapper, legacyscheme.Scheme, nil
} }
func (f *ring1Factory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) { // TODO: unstructured and structured discovery should both gracefully degrade in the presence
// of errors, and it should be possible to get access to the unstructured object typers no matter
// whether the server responds or not. Make the legacy scheme recognize object typer, then we can
// safely fall back to the built in restmapper as a last resort.
func (f *ring1Factory) unstructuredObjectLoader() (meta.RESTMapper, runtime.ObjectTyper, error) {
discoveryClient, err := f.clientAccessFactory.DiscoveryClient() discoveryClient, err := f.clientAccessFactory.DiscoveryClient()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
groupResources, err := discovery.GetAPIGroupResources(discoveryClient) groupResources, err := discovery.GetAPIGroupResources(discoveryClient)
if err != nil && !discoveryClient.Fresh() { if err != nil && !discoveryClient.Fresh() {
discoveryClient.Invalidate() discoveryClient.Invalidate()
@@ -112,10 +115,18 @@ func (f *ring1Factory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectType
interfaces := meta.InterfacesForUnstructuredConversion(legacyscheme.Registry.InterfacesFor) interfaces := meta.InterfacesForUnstructuredConversion(legacyscheme.Registry.InterfacesFor)
mapper := discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, meta.VersionInterfacesFunc(interfaces)) mapper := discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, meta.VersionInterfacesFunc(interfaces))
typer := discovery.NewUnstructuredObjectTyper(groupResources) typer := discovery.NewUnstructuredObjectTyper(groupResources)
expander, err := NewShortcutExpander(mapper, discoveryClient) expander := NewShortcutExpander(mapper, discoveryClient)
return expander, typer, err return expander, typer, err
} }
func (f *ring1Factory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
return NewLazyObjectLoader(f.objectLoader)
}
func (f *ring1Factory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper) {
return NewLazyObjectLoader(f.unstructuredObjectLoader)
}
func (f *ring1Factory) CategoryExpander() categories.CategoryExpander { func (f *ring1Factory) CategoryExpander() categories.CategoryExpander {
legacyExpander := categories.LegacyCategoryExpander legacyExpander := categories.LegacyCategoryExpander

View File

@@ -539,10 +539,7 @@ func TestDiscoveryReplaceAliases(t *testing.T) {
} }
ds := &fakeDiscoveryClient{} ds := &fakeDiscoveryClient{}
mapper, err := NewShortcutExpander(testapi.Default.RESTMapper(), ds) mapper := NewShortcutExpander(testapi.Default.RESTMapper(), ds)
if err != nil {
t.Fatalf("Unable to create shortcut expander, err = %s", err.Error())
}
b := resource.NewBuilder( b := resource.NewBuilder(
&resource.Mapper{ &resource.Mapper{
RESTMapper: mapper, RESTMapper: mapper,

View File

@@ -17,7 +17,6 @@ limitations under the License.
package util package util
import ( import (
"errors"
"strings" "strings"
"github.com/golang/glog" "github.com/golang/glog"
@@ -37,11 +36,8 @@ type shortcutExpander struct {
var _ meta.RESTMapper = &shortcutExpander{} var _ meta.RESTMapper = &shortcutExpander{}
func NewShortcutExpander(delegate meta.RESTMapper, client discovery.DiscoveryInterface) (shortcutExpander, error) { func NewShortcutExpander(delegate meta.RESTMapper, client discovery.DiscoveryInterface) shortcutExpander {
if client == nil { return shortcutExpander{RESTMapper: delegate, discoveryClient: client}
return shortcutExpander{}, errors.New("Please provide discovery client to shortcut expander")
}
return shortcutExpander{RESTMapper: delegate, discoveryClient: client}, nil
} }
func (e shortcutExpander) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { func (e shortcutExpander) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {

View File

@@ -71,10 +71,7 @@ func TestReplaceAliases(t *testing.T) {
} }
ds := &fakeDiscoveryClient{} ds := &fakeDiscoveryClient{}
mapper, err := NewShortcutExpander(testapi.Default.RESTMapper(), ds) mapper := NewShortcutExpander(testapi.Default.RESTMapper(), ds)
if err != nil {
t.Fatalf("Unable to create shortcut expander, err %s", err.Error())
}
for _, test := range tests { for _, test := range tests {
ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) { ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {
@@ -126,10 +123,7 @@ func TestKindFor(t *testing.T) {
} }
ds := &fakeDiscoveryClient{} ds := &fakeDiscoveryClient{}
mapper, err := NewShortcutExpander(testapi.Default.RESTMapper(), ds) mapper := NewShortcutExpander(testapi.Default.RESTMapper(), ds)
if err != nil {
t.Fatalf("Unable to create shortcut expander, err %s", err.Error())
}
for i, test := range tests { for i, test := range tests {
ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) { ds.serverResourcesHandler = func() ([]*metav1.APIResourceList, error) {

View File

@@ -34,6 +34,7 @@ go_library(
"firsthit_restmapper.go", "firsthit_restmapper.go",
"help.go", "help.go",
"interfaces.go", "interfaces.go",
"lazy.go",
"meta.go", "meta.go",
"multirestmapper.go", "multirestmapper.go",
"priority.go", "priority.go",

View File

@@ -0,0 +1,121 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package meta
import (
"sync"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// lazyObject defers loading the mapper and typer until necessary.
type lazyObject struct {
loader func() (RESTMapper, runtime.ObjectTyper, error)
lock sync.Mutex
loaded bool
err error
mapper RESTMapper
typer runtime.ObjectTyper
}
// NewLazyObjectLoader handles unrecoverable errors when creating a RESTMapper / ObjectTyper by
// returning those initialization errors when the interface methods are invoked. This defers the
// initialization and any server calls until a client actually needs to perform the action.
func NewLazyObjectLoader(fn func() (RESTMapper, runtime.ObjectTyper, error)) (RESTMapper, runtime.ObjectTyper) {
obj := &lazyObject{loader: fn}
return obj, obj
}
// init lazily loads the mapper and typer, returning an error if initialization has failed.
func (o *lazyObject) init() error {
o.lock.Lock()
defer o.lock.Unlock()
if o.loaded {
return o.err
}
o.mapper, o.typer, o.err = o.loader()
o.loaded = true
return o.err
}
var _ RESTMapper = &lazyObject{}
var _ runtime.ObjectTyper = &lazyObject{}
func (o *lazyObject) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
if err := o.init(); err != nil {
return schema.GroupVersionKind{}, err
}
return o.mapper.KindFor(resource)
}
func (o *lazyObject) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
if err := o.init(); err != nil {
return []schema.GroupVersionKind{}, err
}
return o.mapper.KindsFor(resource)
}
func (o *lazyObject) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
if err := o.init(); err != nil {
return schema.GroupVersionResource{}, err
}
return o.mapper.ResourceFor(input)
}
func (o *lazyObject) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
if err := o.init(); err != nil {
return []schema.GroupVersionResource{}, err
}
return o.mapper.ResourcesFor(input)
}
func (o *lazyObject) RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error) {
if err := o.init(); err != nil {
return nil, err
}
return o.mapper.RESTMapping(gk, versions...)
}
func (o *lazyObject) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) {
if err := o.init(); err != nil {
return nil, err
}
return o.mapper.RESTMappings(gk, versions...)
}
func (o *lazyObject) ResourceSingularizer(resource string) (singular string, err error) {
if err := o.init(); err != nil {
return "", err
}
return o.mapper.ResourceSingularizer(resource)
}
func (o *lazyObject) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
if err := o.init(); err != nil {
return nil, false, err
}
return o.typer.ObjectKinds(obj)
}
func (o *lazyObject) Recognizes(gvk schema.GroupVersionKind) bool {
if err := o.init(); err != nil {
return false
}
return o.typer.Recognizes(gvk)
}

View File

@@ -17,22 +17,28 @@ limitations under the License.
package discovery package discovery
import ( import (
"fmt" "reflect"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
// UnstructuredObjectTyper provides a runtime.ObjectTyper implmentation for // UnstructuredObjectTyper provides a runtime.ObjectTyper implementation for
// runtime.Unstructured object based on discovery information. // runtime.Unstructured object based on discovery information.
type UnstructuredObjectTyper struct { type UnstructuredObjectTyper struct {
registered map[schema.GroupVersionKind]bool registered map[schema.GroupVersionKind]bool
typers []runtime.ObjectTyper
} }
// NewUnstructuredObjectTyper returns a runtime.ObjectTyper for // NewUnstructuredObjectTyper returns a runtime.ObjectTyper for
// unstructred objects based on discovery information. // unstructured objects based on discovery information. It accepts a list of fallback typers
func NewUnstructuredObjectTyper(groupResources []*APIGroupResources) *UnstructuredObjectTyper { // for handling objects that are not runtime.Unstructured. It does not delegate the Recognizes
dot := &UnstructuredObjectTyper{registered: make(map[schema.GroupVersionKind]bool)} // check, only ObjectKinds.
func NewUnstructuredObjectTyper(groupResources []*APIGroupResources, typers ...runtime.ObjectTyper) *UnstructuredObjectTyper {
dot := &UnstructuredObjectTyper{
registered: make(map[schema.GroupVersionKind]bool),
typers: typers,
}
for _, group := range groupResources { for _, group := range groupResources {
for _, discoveryVersion := range group.Group.Versions { for _, discoveryVersion := range group.Group.Versions {
resources, ok := group.VersionedResources[discoveryVersion.Version] resources, ok := group.VersionedResources[discoveryVersion.Version]
@@ -55,17 +61,29 @@ func NewUnstructuredObjectTyper(groupResources []*APIGroupResources) *Unstructur
// because runtime.Unstructured object should always have group,version,kind // because runtime.Unstructured object should always have group,version,kind
// information set. // information set.
func (d *UnstructuredObjectTyper) ObjectKinds(obj runtime.Object) (gvks []schema.GroupVersionKind, unversionedType bool, err error) { func (d *UnstructuredObjectTyper) ObjectKinds(obj runtime.Object) (gvks []schema.GroupVersionKind, unversionedType bool, err error) {
if _, ok := obj.(runtime.Unstructured); !ok { if _, ok := obj.(runtime.Unstructured); ok {
return nil, false, fmt.Errorf("type %T is invalid for dynamic object typer", obj) gvk := obj.GetObjectKind().GroupVersionKind()
if len(gvk.Kind) == 0 {
return nil, false, runtime.NewMissingKindErr("object has no kind field ")
}
if len(gvk.Version) == 0 {
return nil, false, runtime.NewMissingVersionErr("object has no apiVersion field")
}
return []schema.GroupVersionKind{gvk}, false, nil
} }
gvk := obj.GetObjectKind().GroupVersionKind() var lastErr error
if len(gvk.Kind) == 0 { for _, typer := range d.typers {
return nil, false, runtime.NewMissingKindErr("unstructured object has no kind") gvks, unversioned, err := typer.ObjectKinds(obj)
if err != nil {
lastErr = err
continue
}
return gvks, unversioned, nil
} }
if len(gvk.Version) == 0 { if lastErr == nil {
return nil, false, runtime.NewMissingVersionErr("unstructured object has no version") lastErr = runtime.NewNotRegisteredErrForType(reflect.TypeOf(obj))
} }
return []schema.GroupVersionKind{gvk}, false, nil return nil, false, lastErr
} }
// Recognizes returns true if the provided group,version,kind was in the // Recognizes returns true if the provided group,version,kind was in the

View File

@@ -84,7 +84,7 @@ func NewObjectTyper(resources []*metav1.APIResourceList) (runtime.ObjectTyper, e
// information. // information.
func (ot *ObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { func (ot *ObjectTyper) ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
if _, ok := obj.(*unstructured.Unstructured); !ok { if _, ok := obj.(*unstructured.Unstructured); !ok {
return nil, false, fmt.Errorf("type %T is invalid for dynamic object typer", obj) return nil, false, fmt.Errorf("type %T is invalid for determining dynamic object types", obj)
} }
return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil return []schema.GroupVersionKind{obj.GetObjectKind().GroupVersionKind()}, false, nil
} }