Unify Object and UnstructuredObject

The unified RESTMapper and Typer follow the new rules, but on error will
fallback to the legacy path (while still supporting Unstructured
objects). This allows callers to handle the appropriate distinction
themselves if necessary.

Add a LocalParam() method to the resource.Builder that DRYs up a large
chunk of complicated code in set commands.
This commit is contained in:
Clayton Coleman 2017-11-14 23:03:06 -05:00
parent 7563a0c4d8
commit 04ab96d2bd
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
18 changed files with 135 additions and 156 deletions

View File

@ -189,13 +189,10 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
changeCause := f.Command(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder()
if o.local {
b = b.Local()
} else {
b = b.Unstructured()
}
b = b.ContinueOnError().
b := f.NewBuilder().
Unstructured().
LocalParam(o.local).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).

View File

@ -112,7 +112,7 @@ func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command)
o.Codec = f.JSONEncoder()
var err error
o.Mapper, o.Typer = f.UnstructuredObject()
o.Mapper, o.Typer = f.Object()
if err != nil {
return err
}

View File

@ -128,22 +128,20 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C
}
// build the builder
o.builder = f.NewBuilder()
o.builder = f.NewBuilder().LocalParam(o.local)
if !o.local {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
if err != nil {
return err
}
o.builder = o.builder.Schema(schema)
} else {
o.builder = o.builder.Local()
o.builder.Schema(schema)
}
cmdNamespace, _, err := f.DefaultNamespace()
if err != nil {
return err
}
o.builder = o.builder.NamespaceParam(cmdNamespace).
o.builder.NamespaceParam(cmdNamespace).
ContinueOnError().
FilenameParam(false, &o.FilenameOptions).
Flatten()

View File

@ -18,6 +18,7 @@ package cmd
import (
"bytes"
"fmt"
"net/http"
"testing"
@ -98,30 +99,30 @@ func TestConvertObject(t *testing.T) {
},
}
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
for _, tc := range testcases {
cmd := NewCmdConvert(f, buf)
cmd.Flags().Set("filename", tc.file)
cmd.Flags().Set("output-version", tc.outputVersion)
cmd.Flags().Set("local", "true")
cmd.Flags().Set("output", "go-template")
for _, field := range tc.fields {
buf.Reset()
tf.Printer, _ = printers.NewTemplatePrinter([]byte(field.template))
cmd.Run(cmd, []string{})
if buf.String() != field.expected {
t.Errorf("unexpected output when converting %s to %q, expected: %q, but got %q", tc.file, tc.outputVersion, field.expected, buf.String())
}
t.Run(fmt.Sprintf("%s %s", tc.name, field), func(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConvert(f, buf)
cmd.Flags().Set("filename", tc.file)
cmd.Flags().Set("output-version", tc.outputVersion)
cmd.Flags().Set("local", "true")
cmd.Flags().Set("output", "go-template")
tf.Printer, _ = printers.NewTemplatePrinter([]byte(field.template))
cmd.Run(cmd, []string{})
if buf.String() != field.expected {
t.Errorf("unexpected output when converting %s to %q, expected: %q, but got %q", tc.file, tc.outputVersion, field.expected, buf.String())
}
})
}
}
}

View File

@ -191,13 +191,9 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
changeCause := f.Command(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder()
if o.local {
b = b.Local()
} else {
b = b.Unstructured()
}
b = b.
b := f.NewBuilder().
Unstructured().
LocalParam(o.local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -304,16 +300,10 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
return nil
}
var mapper meta.RESTMapper
if o.local {
mapper, _ = f.Object()
} else {
mapper, _ = f.UnstructuredObject()
}
if o.outputFormat != "" {
return f.PrintObject(cmd, o.local, mapper, outputObj, o.out)
return f.PrintObject(cmd, o.local, r.Mapper().RESTMapper, outputObj, o.out)
}
f.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, dataChangeMsg)
f.PrintSuccess(r.Mapper().RESTMapper, false, o.out, info.Mapping.Resource, info.Name, o.dryrun, dataChangeMsg)
return nil
})
}

View File

@ -230,7 +230,7 @@ func TestGetUnknownSchemaObject(t *testing.T) {
tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
mapper, _ := f.UnstructuredObject()
mapper, _ := f.Object()
m, err := mapper.RESTMapping(schema.GroupKind{Group: "apitest", Kind: "Type"})
if err != nil {
t.Fatal(err)

View File

@ -236,6 +236,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
if len(o.From) != 0 {
b := f.NewBuilder().
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -246,8 +247,6 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.From).
Latest()
} else {
b = b.Local()
}
infos, err := b.Do().Infos()
@ -304,18 +303,16 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
}
b := f.NewBuilder().
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
Flatten()
if !o.Local {
b = b.
LabelSelectorParam(o.Selector).
b.LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.Resources...).
Latest()
} else {
b = b.Local()
}
o.Infos, err = b.Do().Infos()

View File

@ -143,6 +143,7 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder().
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -150,8 +151,7 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
Flatten()
if !o.Local {
builder = builder.
LabelSelectorParam(o.Selector).
builder.LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.Resources...).
Latest()
} else {
@ -161,8 +161,6 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
if len(o.Resources) > 0 {
return resource.LocalResourceError
}
builder = builder.Local()
}
o.Infos, err = builder.Do().Infos()

View File

@ -145,6 +145,7 @@ func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder().
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -152,19 +153,18 @@ func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
Flatten()
if !o.Local {
builder = builder.
LabelSelectorParam(o.Selector).
builder.LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, args...).
Latest()
} else {
// if a --local flag was provided, and a resource was specified in the form
// <resource>/<name>, fail immediately as --local cannot query the api server
// for the specified resource.
// TODO: this should be in the builder - if someone specifies tuples, fail when
// local is true
if len(args) > 0 {
return resource.LocalResourceError
}
builder = builder.Local()
}
o.Infos, err = builder.Do().Infos()

View File

@ -129,6 +129,7 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
o.builder = f.NewBuilder().
LocalParam(o.local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.fileOptions).
@ -136,7 +137,7 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
Flatten()
if !o.local {
o.builder = o.builder.
o.builder.
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
} else {
@ -146,8 +147,6 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
if len(o.resources) > 0 {
return resource.LocalResourceError
}
o.builder = o.builder.Local()
}
o.PrintObject = func(obj runtime.Object) error {

View File

@ -129,7 +129,9 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com
saConfig.serviceAccountName = args[len(args)-1]
resources := args[:len(args)-1]
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder().ContinueOnError().
builder := f.NewBuilder().
LocalParam(saConfig.local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &saConfig.fileNameOptions).
IncludeUninitialized(includeUninitialized).
@ -137,8 +139,6 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com
if !saConfig.local {
builder.ResourceTypeOrNameArgs(saConfig.all, resources...).
Latest()
} else {
builder = builder.Local()
}
saConfig.infos, err = builder.Do().Infos()
if err != nil {

View File

@ -125,7 +125,14 @@ func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
}
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder()
builder := f.NewBuilder().
LocalParam(o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten()
if o.Local {
// if a --local flag was provided, and a resource was specified in the form
// <resource>/<name>, fail immediately as --local cannot query the api server
@ -133,16 +140,7 @@ func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
if len(args) > 0 {
return resource.LocalResourceError
}
builder = builder.Local()
}
builder = builder.
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten()
if !o.Local {
} else {
builder = builder.
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, args...).

View File

@ -244,6 +244,7 @@ type TestFactory struct {
Command string
TmpDir string
CategoryExpander categories.CategoryExpander
SkipDiscovery bool
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
@ -282,10 +283,9 @@ func (f *FakeFactory) FlagSet() *pflag.FlagSet {
}
func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
return legacyscheme.Registry.RESTMapper(), f.tf.Typer
}
func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper) {
if f.tf.SkipDiscovery {
return legacyscheme.Registry.RESTMapper(), f.tf.Typer
}
groupResources := testDynamicResources()
mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructuredConversion(legacyscheme.Registry.InterfacesFor))
typer := discovery.NewUnstructuredObjectTyper(groupResources)
@ -519,7 +519,6 @@ func (f *FakeFactory) PrinterForMapping(cmd *cobra.Command, isLocal bool, output
func (f *FakeFactory) NewBuilder() *resource.Builder {
mapper, typer := f.Object()
unstructuredMapper, unstructuredTyper := f.UnstructuredObject()
return resource.NewBuilder(
&resource.Mapper{
@ -529,8 +528,8 @@ func (f *FakeFactory) NewBuilder() *resource.Builder {
Decoder: f.Decoder(true),
},
&resource.Mapper{
RESTMapper: unstructuredMapper,
ObjectTyper: unstructuredTyper,
RESTMapper: mapper,
ObjectTyper: typer,
ClientMapper: resource.ClientMapperFunc(f.UnstructuredClientForMapping),
Decoder: unstructured.UnstructuredJSONScheme,
},
@ -605,10 +604,9 @@ type fakeAPIFactory struct {
}
func (f *fakeAPIFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
return testapi.Default.RESTMapper(), legacyscheme.Scheme
}
func (f *fakeAPIFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper) {
if f.tf.SkipDiscovery {
return testapi.Default.RESTMapper(), legacyscheme.Scheme
}
groupResources := testDynamicResources()
mapper := discovery.NewRESTMapper(
groupResources,
@ -800,11 +798,7 @@ func (f *fakeAPIFactory) LogsForObject(object, options runtime.Object, timeout t
}
return c.Core().Pods(f.tf.Namespace).GetLogs(t.Name, opts), nil
default:
fqKinds, _, err := legacyscheme.Scheme.ObjectKinds(object)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("cannot get the logs from %v", fqKinds[0])
return nil, fmt.Errorf("cannot get the logs from %T", object)
}
}
@ -813,11 +807,7 @@ func (f *fakeAPIFactory) AttachablePodForObject(object runtime.Object, timeout t
case *api.Pod:
return t, nil
default:
gvks, _, err := legacyscheme.Scheme.ObjectKinds(object)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("cannot attach to %v: not implemented", gvks[0])
return nil, fmt.Errorf("cannot attach to %T: not implemented", object)
}
}
@ -865,7 +855,6 @@ func (f *fakeAPIFactory) PrinterForMapping(cmd *cobra.Command, isLocal bool, out
func (f *fakeAPIFactory) NewBuilder() *resource.Builder {
mapper, typer := f.Object()
unstructuredMapper, unstructuredTyper := f.UnstructuredObject()
return resource.NewBuilder(
&resource.Mapper{
@ -875,8 +864,8 @@ func (f *fakeAPIFactory) NewBuilder() *resource.Builder {
Decoder: f.Decoder(true),
},
&resource.Mapper{
RESTMapper: unstructuredMapper,
ObjectTyper: unstructuredTyper,
RESTMapper: mapper,
ObjectTyper: typer,
ClientMapper: resource.ClientMapperFunc(f.UnstructuredClientForMapping),
Decoder: unstructured.UnstructuredJSONScheme,
},
@ -953,6 +942,46 @@ func testDynamicResources() []*discovery.APIGroupResources {
},
},
},
{
Group: metav1.APIGroup{
Name: "apps",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1beta1"},
{Version: "v1beta2"},
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1beta1": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
},
"v1beta2": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
},
"v1": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
},
},
},
{
Group: metav1.APIGroup{
Name: "autoscaling",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1"},
{Version: "v2beta1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v2beta1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "horizontalpodautoscalers", Namespaced: true, Kind: "HorizontalPodAutoscaler"},
},
"v2beta1": {
{Name: "horizontalpodautoscalers", Namespaced: true, Kind: "HorizontalPodAutoscaler"},
},
},
},
{
Group: metav1.APIGroup{
Name: "storage.k8s.io",

View File

@ -107,7 +107,7 @@ func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []
if err != nil {
return err
}
mapper, _ := f.UnstructuredObject()
mapper, _ := f.Object()
b := f.NewBuilder().Unstructured()
if o.EditMode == NormalEditMode || o.EditMode == ApplyEditMode {
// when do normal edit or apply edit we need to always retrieve the latest resource from server

View File

@ -189,11 +189,7 @@ type ClientAccessFactory interface {
type ObjectMappingFactory interface {
// Returns interfaces for dealing with arbitrary runtime.Objects.
Object() (meta.RESTMapper, runtime.ObjectTyper)
// Returns interfaces for dealing with arbitrary
// runtime.Unstructured. This performs API calls to discover types.
UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper)
// Returns interface for expanding categories like `all`.
// TODO: this should probably return an error if the full expander can't be loaded.
CategoryExpander() categories.CategoryExpander
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended
// for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer.

View File

@ -28,7 +28,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl/plugins"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers"
@ -52,12 +51,8 @@ func (f *ring2Factory) PrinterForCommand(cmd *cobra.Command, isLocal bool, outpu
var mapper meta.RESTMapper
var typer runtime.ObjectTyper
if isLocal {
mapper = legacyscheme.Registry.RESTMapper()
typer = legacyscheme.Scheme
} else {
mapper, typer = f.objectMappingFactory.UnstructuredObject()
}
mapper, typer = f.objectMappingFactory.Object()
// TODO: used by the custom column implementation and the name implementation, break this dependency
decoders := []runtime.Decoder{f.clientAccessFactory.Decoder(true), unstructured.UnstructuredJSONScheme}
encoder := f.clientAccessFactory.JSONEncoder()
@ -131,12 +126,6 @@ func (f *ring2Factory) PrintObject(cmd *cobra.Command, isLocal bool, mapper meta
_, typer := f.objectMappingFactory.Object()
gvks, _, err := typer.ObjectKinds(obj)
// fall back to an unstructured object if we get something unregistered
if runtime.IsNotRegisteredError(err) {
_, typer := f.objectMappingFactory.UnstructuredObject()
gvks, _, err = typer.ObjectKinds(obj)
}
if err != nil {
return err
}
@ -178,7 +167,6 @@ func (f *ring2Factory) NewBuilder() *resource.Builder {
mapper, typer := f.objectMappingFactory.Object()
unstructuredClientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.UnstructuredClientForMapping)
unstructuredMapper, unstructuredTyper := f.objectMappingFactory.Object()
categoryExpander := f.objectMappingFactory.CategoryExpander()
@ -190,8 +178,8 @@ func (f *ring2Factory) NewBuilder() *resource.Builder {
Decoder: f.clientAccessFactory.Decoder(true),
},
&resource.Mapper{
RESTMapper: unstructuredMapper,
ObjectTyper: unstructuredTyper,
RESTMapper: mapper,
ObjectTyper: typer,
ClientMapper: unstructuredClientMapperFunc,
Decoder: unstructured.UnstructuredJSONScheme,
},

View File

@ -73,33 +73,15 @@ func NewObjectMappingFactory(clientAccessFactory ClientAccessFactory) ObjectMapp
return f
}
// objectLoader attempts to perform discovery against the server, and will fall back to
// the built in mapper if necessary. It supports unstructured objects either way, since
// the underlying Scheme supports Unstructured. The mapper will return converters that can
// convert versioned types to unstructured and back.
func (f *ring1Factory) objectLoader() (meta.RESTMapper, runtime.ObjectTyper, error) {
mapper := legacyscheme.Registry.RESTMapper()
discoveryClient, err := f.clientAccessFactory.DiscoveryClient()
if err != nil {
glog.V(5).Infof("Object loader was unable to retrieve a discovery client: %v", err)
return mapper, legacyscheme.Scheme, nil
}
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 = NewShortcutExpander(mapper, discoveryClient)
return mapper, legacyscheme.Scheme, nil
}
// 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()
if err != nil {
return nil, nil, err
glog.V(3).Infof("Unable to get a discovery client to find server resources, falling back to hardcoded types: %v", err)
return legacyscheme.Registry.RESTMapper(), legacyscheme.Scheme, nil
}
groupResources, err := discovery.GetAPIGroupResources(discoveryClient)
@ -108,12 +90,14 @@ func (f *ring1Factory) unstructuredObjectLoader() (meta.RESTMapper, runtime.Obje
groupResources, err = discovery.GetAPIGroupResources(discoveryClient)
}
if err != nil {
return nil, nil, err
glog.V(3).Infof("Unable to retrieve API resources, falling back to hardcoded types: %v", err)
return legacyscheme.Registry.RESTMapper(), legacyscheme.Scheme, nil
}
// allow conversion between typed and unstructured objects
interfaces := meta.InterfacesForUnstructuredConversion(legacyscheme.Registry.InterfacesFor)
mapper := discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, meta.VersionInterfacesFunc(interfaces))
// TODO: should this also indicate it recognizes typed objects?
typer := discovery.NewUnstructuredObjectTyper(groupResources)
expander := NewShortcutExpander(mapper, discoveryClient)
return expander, typer, err
@ -123,10 +107,6 @@ 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 {
legacyExpander := categories.LegacyCategoryExpander

View File

@ -177,6 +177,14 @@ func (b *Builder) Unstructured() *Builder {
return b
}
// LocalParam calls Local() if local is true.
func (b *Builder) LocalParam(local bool) *Builder {
if local {
b.Local()
}
return b
}
// Local will avoid asking the server for results.
func (b *Builder) Local() *Builder {
mapper := *b.mapper