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) changeCause := f.Command(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder() b := f.NewBuilder().
if o.local { Unstructured().
b = b.Local() LocalParam(o.local).
} else { ContinueOnError().
b = b.Unstructured()
}
b = b.ContinueOnError().
NamespaceParam(namespace).DefaultNamespace(). NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized). IncludeUninitialized(includeUninitialized).

View File

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

View File

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

View File

@ -18,6 +18,7 @@ package cmd
import ( import (
"bytes" "bytes"
"fmt"
"net/http" "net/http"
"testing" "testing"
@ -98,6 +99,9 @@ func TestConvertObject(t *testing.T) {
}, },
} }
for _, tc := range testcases {
for _, field := range tc.fields {
t.Run(fmt.Sprintf("%s %s", tc.name, field), func(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -106,22 +110,19 @@ func TestConvertObject(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
for _, tc := range testcases { buf := bytes.NewBuffer([]byte{})
cmd := NewCmdConvert(f, buf) cmd := NewCmdConvert(f, buf)
cmd.Flags().Set("filename", tc.file) cmd.Flags().Set("filename", tc.file)
cmd.Flags().Set("output-version", tc.outputVersion) cmd.Flags().Set("output-version", tc.outputVersion)
cmd.Flags().Set("local", "true") cmd.Flags().Set("local", "true")
cmd.Flags().Set("output", "go-template") cmd.Flags().Set("output", "go-template")
for _, field := range tc.fields {
buf.Reset()
tf.Printer, _ = printers.NewTemplatePrinter([]byte(field.template)) tf.Printer, _ = printers.NewTemplatePrinter([]byte(field.template))
cmd.Run(cmd, []string{}) cmd.Run(cmd, []string{})
if buf.String() != field.expected { 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.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) changeCause := f.Command(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder() b := f.NewBuilder().
if o.local { Unstructured().
b = b.Local() LocalParam(o.local).
} else {
b = b.Unstructured()
}
b = b.
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -304,16 +300,10 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
return nil return nil
} }
var mapper meta.RESTMapper
if o.local {
mapper, _ = f.Object()
} else {
mapper, _ = f.UnstructuredObject()
}
if o.outputFormat != "" { 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 return nil
}) })
} }

View File

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

View File

@ -236,6 +236,7 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
if len(o.From) != 0 { if len(o.From) != 0 {
b := f.NewBuilder(). b := f.NewBuilder().
LocalParam(o.Local).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -246,8 +247,6 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
LabelSelectorParam(o.Selector). LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.From). ResourceTypeOrNameArgs(o.All, o.From).
Latest() Latest()
} else {
b = b.Local()
} }
infos, err := b.Do().Infos() infos, err := b.Do().Infos()
@ -304,18 +303,16 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
} }
b := f.NewBuilder(). b := f.NewBuilder().
LocalParam(o.Local).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
Flatten() Flatten()
if !o.Local { if !o.Local {
b = b. b.LabelSelectorParam(o.Selector).
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.Resources...). ResourceTypeOrNameArgs(o.All, o.Resources...).
Latest() Latest()
} else {
b = b.Local()
} }
o.Infos, err = b.Do().Infos() 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) includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder(). builder := f.NewBuilder().
LocalParam(o.Local).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -150,8 +151,7 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
Flatten() Flatten()
if !o.Local { if !o.Local {
builder = builder. builder.LabelSelectorParam(o.Selector).
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.Resources...). ResourceTypeOrNameArgs(o.All, o.Resources...).
Latest() Latest()
} else { } else {
@ -161,8 +161,6 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
if len(o.Resources) > 0 { if len(o.Resources) > 0 {
return resource.LocalResourceError return resource.LocalResourceError
} }
builder = builder.Local()
} }
o.Infos, err = builder.Do().Infos() 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) includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder(). builder := f.NewBuilder().
LocalParam(o.Local).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -152,19 +153,18 @@ func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
Flatten() Flatten()
if !o.Local { if !o.Local {
builder = builder. builder.LabelSelectorParam(o.Selector).
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, args...). ResourceTypeOrNameArgs(o.All, args...).
Latest() Latest()
} else { } else {
// if a --local flag was provided, and a resource was specified in the form // 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 // <resource>/<name>, fail immediately as --local cannot query the api server
// for the specified resource. // for the specified resource.
// TODO: this should be in the builder - if someone specifies tuples, fail when
// local is true
if len(args) > 0 { if len(args) > 0 {
return resource.LocalResourceError return resource.LocalResourceError
} }
builder = builder.Local()
} }
o.Infos, err = builder.Do().Infos() 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) includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
o.builder = f.NewBuilder(). o.builder = f.NewBuilder().
LocalParam(o.local).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.fileOptions). FilenameParam(enforceNamespace, &o.fileOptions).
@ -136,7 +137,7 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
Flatten() Flatten()
if !o.local { if !o.local {
o.builder = o.builder. o.builder.
ResourceTypeOrNameArgs(o.all, o.resources...). ResourceTypeOrNameArgs(o.all, o.resources...).
Latest() Latest()
} else { } else {
@ -146,8 +147,6 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
if len(o.resources) > 0 { if len(o.resources) > 0 {
return resource.LocalResourceError return resource.LocalResourceError
} }
o.builder = o.builder.Local()
} }
o.PrintObject = func(obj runtime.Object) error { 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] saConfig.serviceAccountName = args[len(args)-1]
resources := args[:len(args)-1] resources := args[:len(args)-1]
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
builder := f.NewBuilder().ContinueOnError(). builder := f.NewBuilder().
LocalParam(saConfig.local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &saConfig.fileNameOptions). FilenameParam(enforceNamespace, &saConfig.fileNameOptions).
IncludeUninitialized(includeUninitialized). IncludeUninitialized(includeUninitialized).
@ -137,8 +139,6 @@ func (saConfig *serviceAccountConfig) Complete(f cmdutil.Factory, cmd *cobra.Com
if !saConfig.local { if !saConfig.local {
builder.ResourceTypeOrNameArgs(saConfig.all, resources...). builder.ResourceTypeOrNameArgs(saConfig.all, resources...).
Latest() Latest()
} else {
builder = builder.Local()
} }
saConfig.infos, err = builder.Do().Infos() saConfig.infos, err = builder.Do().Infos()
if err != nil { 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) 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 o.Local {
// if a --local flag was provided, and a resource was specified in the form // 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 // <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 { if len(args) > 0 {
return resource.LocalResourceError return resource.LocalResourceError
} }
builder = builder.Local() } else {
}
builder = builder.
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten()
if !o.Local {
builder = builder. builder = builder.
LabelSelectorParam(o.Selector). LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, args...). ResourceTypeOrNameArgs(o.All, args...).

View File

@ -244,6 +244,7 @@ type TestFactory struct {
Command string Command string
TmpDir string TmpDir string
CategoryExpander categories.CategoryExpander CategoryExpander categories.CategoryExpander
SkipDiscovery bool
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error) ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
UnstructuredClientForMappingFunc 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) { func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
if f.tf.SkipDiscovery {
return legacyscheme.Registry.RESTMapper(), f.tf.Typer return legacyscheme.Registry.RESTMapper(), f.tf.Typer
} }
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)
@ -519,7 +519,6 @@ 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()
return resource.NewBuilder( return resource.NewBuilder(
&resource.Mapper{ &resource.Mapper{
@ -529,8 +528,8 @@ func (f *FakeFactory) NewBuilder() *resource.Builder {
Decoder: f.Decoder(true), Decoder: f.Decoder(true),
}, },
&resource.Mapper{ &resource.Mapper{
RESTMapper: unstructuredMapper, RESTMapper: mapper,
ObjectTyper: unstructuredTyper, ObjectTyper: typer,
ClientMapper: resource.ClientMapperFunc(f.UnstructuredClientForMapping), ClientMapper: resource.ClientMapperFunc(f.UnstructuredClientForMapping),
Decoder: unstructured.UnstructuredJSONScheme, Decoder: unstructured.UnstructuredJSONScheme,
}, },
@ -605,10 +604,9 @@ type fakeAPIFactory struct {
} }
func (f *fakeAPIFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) { func (f *fakeAPIFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
if f.tf.SkipDiscovery {
return testapi.Default.RESTMapper(), legacyscheme.Scheme return testapi.Default.RESTMapper(), legacyscheme.Scheme
} }
func (f *fakeAPIFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper) {
groupResources := testDynamicResources() groupResources := testDynamicResources()
mapper := discovery.NewRESTMapper( mapper := discovery.NewRESTMapper(
groupResources, 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 return c.Core().Pods(f.tf.Namespace).GetLogs(t.Name, opts), nil
default: default:
fqKinds, _, err := legacyscheme.Scheme.ObjectKinds(object) return nil, fmt.Errorf("cannot get the logs from %T", object)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("cannot get the logs from %v", fqKinds[0])
} }
} }
@ -813,11 +807,7 @@ func (f *fakeAPIFactory) AttachablePodForObject(object runtime.Object, timeout t
case *api.Pod: case *api.Pod:
return t, nil return t, nil
default: default:
gvks, _, err := legacyscheme.Scheme.ObjectKinds(object) return nil, fmt.Errorf("cannot attach to %T: not implemented", object)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("cannot attach to %v: not implemented", gvks[0])
} }
} }
@ -865,7 +855,6 @@ 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()
return resource.NewBuilder( return resource.NewBuilder(
&resource.Mapper{ &resource.Mapper{
@ -875,8 +864,8 @@ func (f *fakeAPIFactory) NewBuilder() *resource.Builder {
Decoder: f.Decoder(true), Decoder: f.Decoder(true),
}, },
&resource.Mapper{ &resource.Mapper{
RESTMapper: unstructuredMapper, RESTMapper: mapper,
ObjectTyper: unstructuredTyper, ObjectTyper: typer,
ClientMapper: resource.ClientMapperFunc(f.UnstructuredClientForMapping), ClientMapper: resource.ClientMapperFunc(f.UnstructuredClientForMapping),
Decoder: unstructured.UnstructuredJSONScheme, 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{ Group: metav1.APIGroup{
Name: "storage.k8s.io", 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 { if err != nil {
return err return err
} }
mapper, _ := f.UnstructuredObject() mapper, _ := f.Object()
b := f.NewBuilder().Unstructured() b := f.NewBuilder().Unstructured()
if o.EditMode == NormalEditMode || o.EditMode == ApplyEditMode { if o.EditMode == NormalEditMode || o.EditMode == ApplyEditMode {
// when do normal edit or apply edit we need to always retrieve the latest resource from server // 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 { type ObjectMappingFactory interface {
// Returns interfaces for dealing with arbitrary runtime.Objects. // Returns interfaces for dealing with arbitrary runtime.Objects.
Object() (meta.RESTMapper, runtime.ObjectTyper) 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`. // 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 CategoryExpander() categories.CategoryExpander
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended // 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. // 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/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/kubectl/plugins" "k8s.io/kubernetes/pkg/kubectl/plugins"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
@ -52,12 +51,8 @@ func (f *ring2Factory) PrinterForCommand(cmd *cobra.Command, isLocal bool, outpu
var mapper meta.RESTMapper var mapper meta.RESTMapper
var typer runtime.ObjectTyper var typer runtime.ObjectTyper
if isLocal { mapper, typer = f.objectMappingFactory.Object()
mapper = legacyscheme.Registry.RESTMapper()
typer = legacyscheme.Scheme
} else {
mapper, typer = f.objectMappingFactory.UnstructuredObject()
}
// 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}
encoder := f.clientAccessFactory.JSONEncoder() encoder := f.clientAccessFactory.JSONEncoder()
@ -131,12 +126,6 @@ func (f *ring2Factory) PrintObject(cmd *cobra.Command, isLocal bool, mapper meta
_, typer := f.objectMappingFactory.Object() _, typer := f.objectMappingFactory.Object()
gvks, _, err := typer.ObjectKinds(obj) 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 { if err != nil {
return err return err
} }
@ -178,7 +167,6 @@ 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.Object()
categoryExpander := f.objectMappingFactory.CategoryExpander() categoryExpander := f.objectMappingFactory.CategoryExpander()
@ -190,8 +178,8 @@ func (f *ring2Factory) NewBuilder() *resource.Builder {
Decoder: f.clientAccessFactory.Decoder(true), Decoder: f.clientAccessFactory.Decoder(true),
}, },
&resource.Mapper{ &resource.Mapper{
RESTMapper: unstructuredMapper, RESTMapper: mapper,
ObjectTyper: unstructuredTyper, ObjectTyper: typer,
ClientMapper: unstructuredClientMapperFunc, ClientMapper: unstructuredClientMapperFunc,
Decoder: unstructured.UnstructuredJSONScheme, Decoder: unstructured.UnstructuredJSONScheme,
}, },

View File

@ -73,33 +73,15 @@ func NewObjectMappingFactory(clientAccessFactory ClientAccessFactory) ObjectMapp
return f 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) { func (f *ring1Factory) objectLoader() (meta.RESTMapper, runtime.ObjectTyper, error) {
mapper := legacyscheme.Registry.RESTMapper()
discoveryClient, err := f.clientAccessFactory.DiscoveryClient() discoveryClient, err := f.clientAccessFactory.DiscoveryClient()
if err != nil { if err != nil {
glog.V(5).Infof("Object loader was unable to retrieve a discovery client: %v", err) glog.V(3).Infof("Unable to get a discovery client to find server resources, falling back to hardcoded types: %v", err)
return mapper, legacyscheme.Scheme, nil return legacyscheme.Registry.RESTMapper(), 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
} }
groupResources, err := discovery.GetAPIGroupResources(discoveryClient) groupResources, err := discovery.GetAPIGroupResources(discoveryClient)
@ -108,12 +90,14 @@ func (f *ring1Factory) unstructuredObjectLoader() (meta.RESTMapper, runtime.Obje
groupResources, err = discovery.GetAPIGroupResources(discoveryClient) groupResources, err = discovery.GetAPIGroupResources(discoveryClient)
} }
if err != nil { 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 // allow conversion between typed and unstructured objects
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))
// TODO: should this also indicate it recognizes typed objects?
typer := discovery.NewUnstructuredObjectTyper(groupResources) typer := discovery.NewUnstructuredObjectTyper(groupResources)
expander := NewShortcutExpander(mapper, discoveryClient) expander := NewShortcutExpander(mapper, discoveryClient)
return expander, typer, err return expander, typer, err
@ -123,10 +107,6 @@ func (f *ring1Factory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
return NewLazyObjectLoader(f.objectLoader) 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

@ -177,6 +177,14 @@ func (b *Builder) Unstructured() *Builder {
return b 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. // Local will avoid asking the server for results.
func (b *Builder) Local() *Builder { func (b *Builder) Local() *Builder {
mapper := *b.mapper mapper := *b.mapper