Unify unstructured and versioned object in resource.Builder

resource.Builder should be aware of both paths, and the caller is
responsible for determining the different path via use.
pull/6/head
Clayton Coleman 2017-11-13 22:56:59 -05:00
parent 98e0c69907
commit 0229fd4bd1
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
10 changed files with 273 additions and 123 deletions

View File

@ -183,11 +183,28 @@ func (f *ring2Factory) PrintResourceInfoForCommand(cmd *cobra.Command, info *res
// NewBuilder returns a new resource builder for structured api objects. // NewBuilder returns a new resource builder for structured api objects.
func (f *ring2Factory) NewBuilder() *resource.Builder { func (f *ring2Factory) NewBuilder() *resource.Builder {
clientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.ClientForMapping) clientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.ClientForMapping)
mapper, typer := f.objectMappingFactory.Object() mapper, typer := f.objectMappingFactory.Object()
unstructuredClientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.UnstructuredClientForMapping)
unstructuredMapper, unstructuredTyper, _ := f.objectMappingFactory.UnstructuredObject()
categoryExpander := f.objectMappingFactory.CategoryExpander() categoryExpander := f.objectMappingFactory.CategoryExpander()
return resource.NewBuilder(mapper, categoryExpander, typer, clientMapperFunc, f.clientAccessFactory.Decoder(true)) return resource.NewBuilder(
&resource.Mapper{
RESTMapper: mapper,
ObjectTyper: typer,
ClientMapper: clientMapperFunc,
Decoder: f.clientAccessFactory.Decoder(true),
},
&resource.Mapper{
RESTMapper: unstructuredMapper,
ObjectTyper: unstructuredTyper,
ClientMapper: unstructuredClientMapperFunc,
Decoder: unstructured.UnstructuredJSONScheme,
},
categoryExpander,
)
} }
// PluginLoader loads plugins from a path set by the KUBECTL_PLUGINS_PATH env var. // PluginLoader loads plugins from a path set by the KUBECTL_PLUGINS_PATH env var.

View File

@ -108,7 +108,9 @@ func (f *ring1Factory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectType
return nil, nil, err return nil, nil, err
} }
mapper := discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, meta.InterfacesForUnstructured) // allow conversion between typed and unstructured objects
interfaces := meta.InterfacesForUnstructuredConversion(legacyscheme.Registry.InterfacesFor)
mapper := discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, meta.VersionInterfacesFunc(interfaces))
typer := discovery.NewUnstructuredObjectTyper(groupResources) typer := discovery.NewUnstructuredObjectTyper(groupResources)
expander, err := NewShortcutExpander(mapper, discoveryClient) expander, err := NewShortcutExpander(mapper, discoveryClient)
return expander, typer, err return expander, typer, err

View File

@ -543,7 +543,16 @@ func TestDiscoveryReplaceAliases(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unable to create shortcut expander, err = %s", err.Error()) t.Fatalf("Unable to create shortcut expander, err = %s", err.Error())
} }
b := resource.NewBuilder(mapper, categories.LegacyCategoryExpander, legacyscheme.Scheme, fakeClient(), testapi.Default.Codec()) b := resource.NewBuilder(
&resource.Mapper{
RESTMapper: mapper,
ObjectTyper: legacyscheme.Scheme,
ClientMapper: fakeClient(),
Decoder: testapi.Default.Codec(),
},
nil,
categories.LegacyCategoryExpander,
)
for _, test := range tests { for _, test := range tests {
replaced := b.ReplaceAliases(test.arg) replaced := b.ReplaceAliases(test.arg)

View File

@ -26,9 +26,7 @@ import (
"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"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
@ -46,6 +44,7 @@ const defaultHttpGetAttempts int = 3
// over using the Visitor interface. // over using the Visitor interface.
type Builder struct { type Builder struct {
mapper *Mapper mapper *Mapper
unstructured *Mapper
categoryExpander categories.CategoryExpander categoryExpander categories.CategoryExpander
errs []error errs []error
@ -118,9 +117,10 @@ type resourceTuple struct {
} }
// NewBuilder creates a builder that operates on generic objects. // NewBuilder creates a builder that operates on generic objects.
func NewBuilder(mapper meta.RESTMapper, categoryExpander categories.CategoryExpander, typer runtime.ObjectTyper, clientMapper ClientMapper, decoder runtime.Decoder) *Builder { func NewBuilder(mapper, unstructured *Mapper, categoryExpander categories.CategoryExpander) *Builder {
return &Builder{ return &Builder{
mapper: &Mapper{typer, mapper, clientMapper, decoder}, mapper: mapper,
unstructured: unstructured,
categoryExpander: categoryExpander, categoryExpander: categoryExpander,
requireObject: true, requireObject: true,
} }
@ -166,23 +166,31 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename
return b return b
} }
// Local wraps the builder's clientMapper in a DisabledClientMapperForMapping // Unstructured updates the builder so that it will request and send unstructured
func (b *Builder) Local(mapperFunc ClientMapperFunc) *Builder { // objects by default. Calling this method resets Local().
b.mapper.ClientMapper = DisabledClientForMapping{ClientMapper: ClientMapperFunc(mapperFunc)} func (b *Builder) Unstructured() *Builder {
if b.unstructured == nil {
b.errs = append(b.errs, fmt.Errorf("no unstructured builder provided"))
return b
}
b.mapper = b.unstructured
return b return b
} }
// Unstructured updates the builder's ClientMapper, RESTMapper, // Local will avoid asking the server for results.
// ObjectTyper, and codec for working with unstructured api objects func (b *Builder) Local() *Builder {
func (b *Builder) Unstructured(mapperFunc ClientMapperFunc, mapper meta.RESTMapper, typer runtime.ObjectTyper) *Builder { mapper := *b.mapper
b.mapper.RESTMapper = mapper mapper.ClientMapper = DisabledClientForMapping{ClientMapper: mapper.ClientMapper}
b.mapper.ObjectTyper = typer b.mapper = &mapper
b.mapper.Decoder = unstructured.UnstructuredJSONScheme
b.mapper.ClientMapper = ClientMapperFunc(mapperFunc)
return b return b
} }
// Mapper returns a copy of the current mapper.
func (b *Builder) Mapper() *Mapper {
mapper := *b.mapper
return &mapper
}
// URL accepts a number of URLs directly. // URL accepts a number of URLs directly.
func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder { func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder {
for _, u := range urls { for _, u := range urls {
@ -755,7 +763,13 @@ func (b *Builder) visitByResource() *Result {
} }
} }
info := NewInfo(client, mapping, selectorNamespace, tuple.Name, b.export) info := &Info{
Client: client,
Mapping: mapping,
Namespace: selectorNamespace,
Name: tuple.Name,
Export: b.export,
}
items = append(items, info) items = append(items, info)
} }
@ -814,7 +828,13 @@ func (b *Builder) visitByName() *Result {
visitors := []Visitor{} visitors := []Visitor{}
for _, name := range b.names { for _, name := range b.names {
info := NewInfo(client, mapping, selectorNamespace, name, b.export) info := &Info{
Client: client,
Mapping: mapping,
Namespace: selectorNamespace,
Name: name,
Export: b.export,
}
visitors = append(visitors, info) visitors = append(visitors, info)
} }
result.visitor = VisitorList(visitors) result.visitor = VisitorList(visitors)
@ -875,6 +895,7 @@ func (b *Builder) visitByPaths() *Result {
// for further iteration. // for further iteration.
func (b *Builder) Do() *Result { func (b *Builder) Do() *Result {
r := b.visitorResult() r := b.visitorResult()
r.mapper = b.Mapper()
if r.err != nil { if r.err != nil {
return r return r
} }

View File

@ -263,8 +263,25 @@ var aRC string = `
} }
` `
func newDefaultBuilder() *Builder {
return newDefaultBuilderWith(fakeClient())
}
func newDefaultBuilderWith(client ClientMapper) *Builder {
return NewBuilder(
&Mapper{
RESTMapper: restmapper,
ObjectTyper: scheme.Scheme,
ClientMapper: client,
Decoder: corev1Codec,
},
nil,
categories.LegacyCategoryExpander,
)
}
func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) { func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../test/fixtures/pkg/kubectl/builder/kitten-rc.yaml"}}) FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../test/fixtures/pkg/kubectl/builder/kitten-rc.yaml"}})
test := &testVisitor{} test := &testVisitor{}
@ -279,10 +296,11 @@ func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) {
if info.Name != "update-demo-kitten" || info.Namespace != "" || info.Object == nil { if info.Name != "update-demo-kitten" || info.Namespace != "" || info.Object == nil {
t.Errorf("unexpected info: %#v", info) t.Errorf("unexpected info: %#v", info)
} }
version, ok := info.VersionedObject.(*v1.ReplicationController) obj := info.AsVersioned()
version, ok := obj.(*v1.ReplicationController)
// versioned object does not have defaulting applied // versioned object does not have defaulting applied
if info.VersionedObject == nil || !ok || version.Spec.Replicas != nil { if obj == nil || !ok || version.Spec.Replicas != nil {
t.Errorf("unexpected versioned object: %#v", info.VersionedObject) t.Errorf("unexpected versioned object: %#v", obj)
} }
} }
@ -303,8 +321,7 @@ func TestNodeBuilder(t *testing.T) {
w.Write([]byte(runtime.EncodeOrDie(corev1Codec, node))) w.Write([]byte(runtime.EncodeOrDie(corev1Codec, node)))
}() }()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().NamespaceParam("test").Stream(r, "STDIN")
NamespaceParam("test").Stream(r, "STDIN")
test := &testVisitor{} test := &testVisitor{}
@ -367,7 +384,7 @@ func TestPathBuilderWithMultiple(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: test.recursive, Filenames: []string{test.directory}}). FilenameParam(false, &FilenameOptions{Recursive: test.recursive, Filenames: []string{test.directory}}).
NamespaceParam("test").DefaultNamespace() NamespaceParam("test").DefaultNamespace()
@ -426,7 +443,7 @@ func TestPathBuilderWithMultipleInvalid(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: test.recursive, Filenames: []string{test.directory}}). FilenameParam(false, &FilenameOptions{Recursive: test.recursive, Filenames: []string{test.directory}}).
NamespaceParam("test").DefaultNamespace() NamespaceParam("test").DefaultNamespace()
@ -441,7 +458,7 @@ func TestPathBuilderWithMultipleInvalid(t *testing.T) {
} }
func TestDirectoryBuilder(t *testing.T) { func TestDirectoryBuilder(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy"}}). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy"}}).
NamespaceParam("test").DefaultNamespace() NamespaceParam("test").DefaultNamespace()
@ -472,7 +489,7 @@ func TestNamespaceOverride(t *testing.T) {
})) }))
defer s.Close() defer s.Close()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
NamespaceParam("test") NamespaceParam("test")
@ -483,7 +500,7 @@ func TestNamespaceOverride(t *testing.T) {
t.Fatalf("unexpected response: %v %#v", err, test.Infos) t.Fatalf("unexpected response: %v %#v", err, test.Infos)
} }
b = NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b = newDefaultBuilder().
FilenameParam(true, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}). FilenameParam(true, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
NamespaceParam("test") NamespaceParam("test")
@ -503,7 +520,7 @@ func TestURLBuilder(t *testing.T) {
})) }))
defer s.Close() defer s.Close()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
NamespaceParam("foo") NamespaceParam("foo")
@ -532,7 +549,7 @@ func TestURLBuilderRequireNamespace(t *testing.T) {
})) }))
defer s.Close() defer s.Close()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
NamespaceParam("test").RequireNamespace() NamespaceParam("test").RequireNamespace()
@ -547,10 +564,9 @@ func TestURLBuilderRequireNamespace(t *testing.T) {
func TestResourceByName(t *testing.T) { func TestResourceByName(t *testing.T) {
pods, _ := testData() pods, _ := testData()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
}), corev1Codec). })).NamespaceParam("test")
NamespaceParam("test")
test := &testVisitor{} test := &testVisitor{}
singleItemImplied := false singleItemImplied := false
@ -580,12 +596,12 @@ func TestResourceByName(t *testing.T) {
func TestMultipleResourceByTheSameName(t *testing.T) { func TestMultipleResourceByTheSameName(t *testing.T) {
pods, svcs := testData() pods, svcs := testData()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
"/namespaces/test/pods/baz": runtime.EncodeOrDie(corev1Codec, &pods.Items[1]), "/namespaces/test/pods/baz": runtime.EncodeOrDie(corev1Codec, &pods.Items[1]),
"/namespaces/test/services/foo": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]), "/namespaces/test/services/foo": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]),
"/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]), "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]),
}), corev1Codec). })).
NamespaceParam("test") NamespaceParam("test")
test := &testVisitor{} test := &testVisitor{}
@ -632,11 +648,10 @@ func TestRequestModifier(t *testing.T) {
func TestResourceNames(t *testing.T) { func TestResourceNames(t *testing.T) {
pods, svc := testData() pods, svc := testData()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
"/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]), "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]),
}), corev1Codec). })).NamespaceParam("test")
NamespaceParam("test")
test := &testVisitor{} test := &testVisitor{}
@ -660,11 +675,10 @@ func TestResourceNames(t *testing.T) {
func TestResourceNamesWithoutResource(t *testing.T) { func TestResourceNamesWithoutResource(t *testing.T) {
pods, svc := testData() pods, svc := testData()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
"/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]), "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]),
}), corev1Codec). })).NamespaceParam("test")
NamespaceParam("test")
test := &testVisitor{} test := &testVisitor{}
@ -681,8 +695,7 @@ func TestResourceNamesWithoutResource(t *testing.T) {
} }
func TestResourceByNameWithoutRequireObject(t *testing.T) { func TestResourceByNameWithoutRequireObject(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{}), corev1Codec). b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{})).NamespaceParam("test")
NamespaceParam("test")
test := &testVisitor{} test := &testVisitor{}
singleItemImplied := false singleItemImplied := false
@ -715,9 +728,9 @@ func TestResourceByNameWithoutRequireObject(t *testing.T) {
func TestResourceByNameAndEmptySelector(t *testing.T) { func TestResourceByNameAndEmptySelector(t *testing.T) {
pods, _ := testData() pods, _ := testData()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]), "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
}), corev1Codec). })).
NamespaceParam("test"). NamespaceParam("test").
LabelSelectorParam(""). LabelSelectorParam("").
ResourceTypeOrNameArgs(true, "pods", "foo") ResourceTypeOrNameArgs(true, "pods", "foo")
@ -743,10 +756,10 @@ func TestResourceByNameAndEmptySelector(t *testing.T) {
func TestLabelSelector(t *testing.T) { func TestLabelSelector(t *testing.T) {
pods, svc := testData() pods, svc := testData()
labelKey := metav1.LabelSelectorQueryParam(corev1GV.String()) labelKey := metav1.LabelSelectorQueryParam(corev1GV.String())
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods), "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
"/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc), "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
}), corev1Codec). })).
LabelSelectorParam("a=b"). LabelSelectorParam("a=b").
NamespaceParam("test"). NamespaceParam("test").
Flatten() Flatten()
@ -774,7 +787,7 @@ func TestLabelSelector(t *testing.T) {
} }
func TestLabelSelectorRequiresKnownTypes(t *testing.T) { func TestLabelSelectorRequiresKnownTypes(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
LabelSelectorParam("a=b"). LabelSelectorParam("a=b").
NamespaceParam("test"). NamespaceParam("test").
ResourceTypes("unknown") ResourceTypes("unknown")
@ -787,10 +800,10 @@ func TestLabelSelectorRequiresKnownTypes(t *testing.T) {
func TestFieldSelector(t *testing.T) { func TestFieldSelector(t *testing.T) {
pods, svc := testData() pods, svc := testData()
fieldKey := metav1.FieldSelectorQueryParam(corev1GV.String()) fieldKey := metav1.FieldSelectorQueryParam(corev1GV.String())
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods), "/namespaces/test/pods?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
"/namespaces/test/services?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc), "/namespaces/test/services?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
}), corev1Codec). })).
FieldSelectorParam("a=b"). FieldSelectorParam("a=b").
NamespaceParam("test"). NamespaceParam("test").
Flatten() Flatten()
@ -818,7 +831,7 @@ func TestFieldSelector(t *testing.T) {
} }
func TestFieldSelectorRequiresKnownTypes(t *testing.T) { func TestFieldSelectorRequiresKnownTypes(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
FieldSelectorParam("a=b"). FieldSelectorParam("a=b").
NamespaceParam("test"). NamespaceParam("test").
ResourceTypes("unknown") ResourceTypes("unknown")
@ -829,7 +842,7 @@ func TestFieldSelectorRequiresKnownTypes(t *testing.T) {
} }
func TestSingleResourceType(t *testing.T) { func TestSingleResourceType(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
LabelSelectorParam("a=b"). LabelSelectorParam("a=b").
SingleResourceType(). SingleResourceType().
ResourceTypeOrNameArgs(true, "pods,services") ResourceTypeOrNameArgs(true, "pods,services")
@ -898,8 +911,7 @@ func TestResourceTuple(t *testing.T) {
"/nodes/foo": runtime.EncodeOrDie(corev1Codec, &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}), "/nodes/foo": runtime.EncodeOrDie(corev1Codec, &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}),
} }
} }
b := newDefaultBuilderWith(fakeClientWith(k, t, expectedRequests)).
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith(k, t, expectedRequests), corev1Codec).
NamespaceParam("test").DefaultNamespace(). NamespaceParam("test").DefaultNamespace().
ResourceTypeOrNameArgs(true, testCase.args...).RequireObject(requireObject) ResourceTypeOrNameArgs(true, testCase.args...).RequireObject(requireObject)
@ -930,7 +942,7 @@ func TestResourceTuple(t *testing.T) {
func TestStream(t *testing.T) { func TestStream(t *testing.T) {
r, pods, rc := streamTestData() r, pods, rc := streamTestData()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
NamespaceParam("test").Stream(r, "STDIN").Flatten() NamespaceParam("test").Stream(r, "STDIN").Flatten()
test := &testVisitor{} test := &testVisitor{}
@ -947,7 +959,7 @@ func TestStream(t *testing.T) {
func TestYAMLStream(t *testing.T) { func TestYAMLStream(t *testing.T) {
r, pods, rc := streamYAMLTestData() r, pods, rc := streamYAMLTestData()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
NamespaceParam("test").Stream(r, "STDIN").Flatten() NamespaceParam("test").Stream(r, "STDIN").Flatten()
test := &testVisitor{} test := &testVisitor{}
@ -964,7 +976,7 @@ func TestYAMLStream(t *testing.T) {
func TestMultipleObject(t *testing.T) { func TestMultipleObject(t *testing.T) {
r, pods, svc := streamTestData() r, pods, svc := streamTestData()
obj, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). obj, err := newDefaultBuilder().
NamespaceParam("test").Stream(r, "STDIN").Flatten(). NamespaceParam("test").Stream(r, "STDIN").Flatten().
Do().Object() Do().Object()
@ -986,7 +998,7 @@ func TestMultipleObject(t *testing.T) {
func TestContinueOnErrorVisitor(t *testing.T) { func TestContinueOnErrorVisitor(t *testing.T) {
r, _, _ := streamTestData() r, _, _ := streamTestData()
req := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). req := newDefaultBuilder().
ContinueOnError(). ContinueOnError().
NamespaceParam("test").Stream(r, "STDIN").Flatten(). NamespaceParam("test").Stream(r, "STDIN").Flatten().
Do() Do()
@ -1015,7 +1027,7 @@ func TestContinueOnErrorVisitor(t *testing.T) {
} }
func TestSingleItemImpliedObject(t *testing.T) { func TestSingleItemImpliedObject(t *testing.T) {
obj, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). obj, err := newDefaultBuilder().
NamespaceParam("test").DefaultNamespace(). NamespaceParam("test").DefaultNamespace().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}).
Flatten(). Flatten().
@ -1035,7 +1047,7 @@ func TestSingleItemImpliedObject(t *testing.T) {
} }
func TestSingleItemImpliedObjectNoExtension(t *testing.T) { func TestSingleItemImpliedObjectNoExtension(t *testing.T) {
obj, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). obj, err := newDefaultBuilder().
NamespaceParam("test").DefaultNamespace(). NamespaceParam("test").DefaultNamespace().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/pod"}}). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/pod"}}).
Flatten(). Flatten().
@ -1057,7 +1069,7 @@ func TestSingleItemImpliedObjectNoExtension(t *testing.T) {
func TestSingleItemImpliedRootScopedObject(t *testing.T) { func TestSingleItemImpliedRootScopedObject(t *testing.T) {
node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: v1.NodeSpec{ExternalID: "test"}} node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: v1.NodeSpec{ExternalID: "test"}}
r := streamTestObject(node) r := streamTestObject(node)
infos, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). infos, err := newDefaultBuilder().
NamespaceParam("test").DefaultNamespace(). NamespaceParam("test").DefaultNamespace().
Stream(r, "STDIN"). Stream(r, "STDIN").
Flatten(). Flatten().
@ -1082,9 +1094,9 @@ func TestSingleItemImpliedRootScopedObject(t *testing.T) {
func TestListObject(t *testing.T) { func TestListObject(t *testing.T) {
pods, _ := testData() pods, _ := testData()
labelKey := metav1.LabelSelectorQueryParam(corev1GV.String()) labelKey := metav1.LabelSelectorQueryParam(corev1GV.String())
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods), "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
}), corev1Codec). })).
LabelSelectorParam("a=b"). LabelSelectorParam("a=b").
NamespaceParam("test"). NamespaceParam("test").
ResourceTypeOrNameArgs(true, "pods"). ResourceTypeOrNameArgs(true, "pods").
@ -1115,10 +1127,10 @@ func TestListObject(t *testing.T) {
func TestListObjectWithDifferentVersions(t *testing.T) { func TestListObjectWithDifferentVersions(t *testing.T) {
pods, svc := testData() pods, svc := testData()
labelKey := metav1.LabelSelectorQueryParam(corev1GV.String()) labelKey := metav1.LabelSelectorQueryParam(corev1GV.String())
obj, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ obj, err := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods), "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
"/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc), "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
}), corev1Codec). })).
LabelSelectorParam("a=b"). LabelSelectorParam("a=b").
NamespaceParam("test"). NamespaceParam("test").
ResourceTypeOrNameArgs(true, "pods,services"). ResourceTypeOrNameArgs(true, "pods,services").
@ -1141,12 +1153,12 @@ func TestListObjectWithDifferentVersions(t *testing.T) {
func TestWatch(t *testing.T) { func TestWatch(t *testing.T) {
_, svc := testData() _, svc := testData()
w, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ w, err := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/services?fieldSelector=metadata.name%3Dredis-master&resourceVersion=12&watch=true": watchBody(watch.Event{ "/namespaces/test/services?fieldSelector=metadata.name%3Dredis-master&resourceVersion=12&watch=true": watchBody(watch.Event{
Type: watch.Added, Type: watch.Added,
Object: &svc.Items[0], Object: &svc.Items[0],
}), }),
}), corev1Codec). })).
NamespaceParam("test").DefaultNamespace(). NamespaceParam("test").DefaultNamespace().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/redis-master-service.yaml"}}).Flatten(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/redis-master-service.yaml"}}).Flatten().
Do().Watch("12") Do().Watch("12")
@ -1173,7 +1185,7 @@ func TestWatch(t *testing.T) {
} }
func TestWatchMultipleError(t *testing.T) { func TestWatchMultipleError(t *testing.T) {
_, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). _, err := newDefaultBuilder().
NamespaceParam("test").DefaultNamespace(). NamespaceParam("test").DefaultNamespace().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}).Flatten(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}).Flatten().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}).Flatten(). FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}).Flatten().
@ -1196,11 +1208,11 @@ func TestLatest(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "15"}, ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "15"},
} }
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{ b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, newPod), "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, newPod),
"/namespaces/test/pods/bar": runtime.EncodeOrDie(corev1Codec, newPod2), "/namespaces/test/pods/bar": runtime.EncodeOrDie(corev1Codec, newPod2),
"/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, newSvc), "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, newSvc),
}), corev1Codec). })).
NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest() NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest()
test := &testVisitor{} test := &testVisitor{}
@ -1232,7 +1244,7 @@ func TestReceiveMultipleErrors(t *testing.T) {
w2.Write([]byte(runtime.EncodeOrDie(corev1Codec, &svc.Items[0]))) w2.Write([]byte(runtime.EncodeOrDie(corev1Codec, &svc.Items[0])))
}() }()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec). b := newDefaultBuilder().
Stream(r, "1").Stream(r2, "2"). Stream(r, "1").Stream(r2, "2").
ContinueOnError() ContinueOnError()

View File

@ -25,17 +25,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
// DisabledClientForMapping allows callers to avoid allowing remote calls when handling // Mapper is a convenience struct for holding references to the interfaces
// resources.
type DisabledClientForMapping struct {
ClientMapper
}
func (f DisabledClientForMapping) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) {
return nil, nil
}
// Mapper is a convenience struct for holding references to the three interfaces
// needed to create Info for arbitrary objects. // needed to create Info for arbitrary objects.
type Mapper struct { type Mapper struct {
runtime.ObjectTyper runtime.ObjectTyper
@ -44,17 +34,27 @@ type Mapper struct {
runtime.Decoder runtime.Decoder
} }
// AcceptUnrecognizedObjects will return a mapper that will tolerate objects
// that are not recognized by the RESTMapper, returning mappings that can
// perform minimal transformation. Allows working in disconnected mode, or with
// objects that the server does not recognize. Returned resource.Info objects
// may have empty resource fields and nil clients.
func (m *Mapper) AcceptUnrecognizedObjects() *Mapper {
copied := *m
copied.RESTMapper = NewRelaxedRESTMapper(m.RESTMapper)
copied.ClientMapper = NewRelaxedClientMapper(m.ClientMapper)
return &copied
}
// InfoForData creates an Info object for the given data. An error is returned // InfoForData creates an Info object for the given data. An error is returned
// if any of the decoding or client lookup steps fail. Name and namespace will be // if any of the decoding or client lookup steps fail. Name and namespace will be
// set into Info if the mapping's MetadataAccessor can retrieve them. // set into Info if the mapping's MetadataAccessor can retrieve them.
func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
versions := &runtime.VersionedObjects{} obj, gvk, err := m.Decode(data, nil, nil)
_, gvk, err := m.Decode(data, nil, versions)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to decode %q: %v", source, err) return nil, fmt.Errorf("unable to decode %q: %v", source, err)
} }
obj, versioned := versions.Last(), versions.First()
mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version) mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to recognize %q: %v", source, err) return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
@ -70,14 +70,15 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj) resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)
return &Info{ return &Info{
Mapping: mapping, Client: client,
Client: client, Mapping: mapping,
Source: source,
Namespace: namespace, Namespace: namespace,
Name: name, Name: name,
Source: source,
VersionedObject: versioned,
Object: obj,
ResourceVersion: resourceVersion, ResourceVersion: resourceVersion,
Object: obj,
}, nil }, nil
} }
@ -99,6 +100,7 @@ func (m *Mapper) InfoForObject(obj runtime.Object, preferredGVKs []schema.GroupV
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to recognize %v: %v", groupVersionKind, err) return nil, fmt.Errorf("unable to recognize %v: %v", groupVersionKind, err)
} }
client, err := m.ClientForMapping(mapping) client, err := m.ClientForMapping(mapping)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err) return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err)
@ -107,13 +109,14 @@ func (m *Mapper) InfoForObject(obj runtime.Object, preferredGVKs []schema.GroupV
namespace, _ := mapping.MetadataAccessor.Namespace(obj) namespace, _ := mapping.MetadataAccessor.Namespace(obj)
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj) resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)
return &Info{ return &Info{
Mapping: mapping, Client: client,
Client: client, Mapping: mapping,
Namespace: namespace,
Name: name,
Object: obj, Namespace: namespace,
Name: name,
ResourceVersion: resourceVersion, ResourceVersion: resourceVersion,
Object: obj,
}, nil }, nil
} }
@ -152,3 +155,83 @@ func preferredObjectKind(possibilities []schema.GroupVersionKind, preferences []
// Just pick the first // Just pick the first
return possibilities[0] return possibilities[0]
} }
// DisabledClientForMapping allows callers to avoid allowing remote calls when handling
// resources.
type DisabledClientForMapping struct {
ClientMapper
}
func (f DisabledClientForMapping) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) {
return nil, nil
}
// NewRelaxedClientMapper will return a nil mapping if the object is not a recognized resource.
func NewRelaxedClientMapper(mapper ClientMapper) ClientMapper {
return relaxedClientMapper{mapper}
}
type relaxedClientMapper struct {
ClientMapper
}
func (f relaxedClientMapper) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) {
if len(mapping.Resource) == 0 {
return nil, nil
}
return f.ClientMapper.ClientForMapping(mapping)
}
// NewRelaxedRESTMapper returns a RESTMapper that will tolerate mappings that don't exist in provided
// RESTMapper, returning a mapping that is a best effort against the current server. This allows objects
// that the server does not recognize to still be loaded.
func NewRelaxedRESTMapper(mapper meta.RESTMapper) meta.RESTMapper {
return relaxedMapper{mapper}
}
type relaxedMapper struct {
meta.RESTMapper
}
func (m relaxedMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
mapping, err := m.RESTMapper.RESTMapping(gk, versions...)
if err != nil && meta.IsNoMatchError(err) && len(versions) > 0 {
return &meta.RESTMapping{
GroupVersionKind: gk.WithVersion(versions[0]),
MetadataAccessor: meta.NewAccessor(),
Scope: meta.RESTScopeRoot,
ObjectConvertor: identityConvertor{},
}, nil
}
return mapping, err
}
func (m relaxedMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
mappings, err := m.RESTMapper.RESTMappings(gk, versions...)
if err != nil && meta.IsNoMatchError(err) && len(versions) > 0 {
return []*meta.RESTMapping{
{
GroupVersionKind: gk.WithVersion(versions[0]),
MetadataAccessor: meta.NewAccessor(),
Scope: meta.RESTScopeRoot,
ObjectConvertor: identityConvertor{},
},
}, nil
}
return mappings, err
}
type identityConvertor struct{}
var _ runtime.ObjectConvertor = identityConvertor{}
func (c identityConvertor) Convert(in interface{}, out interface{}, context interface{}) error {
return fmt.Errorf("unable to convert objects across pointers")
}
func (c identityConvertor) ConvertToVersion(in runtime.Object, gv runtime.GroupVersioner) (out runtime.Object, err error) {
return in, nil
}
func (c identityConvertor) ConvertFieldLabel(version string, kind string, label string, value string) (string, string, error) {
return "", "", fmt.Errorf("unable to convert field labels")
}

View File

@ -41,6 +41,7 @@ type Result struct {
singleItemImplied bool singleItemImplied bool
targetsSingleItems bool targetsSingleItems bool
mapper *Mapper
ignoreErrors []utilerrors.Matcher ignoreErrors []utilerrors.Matcher
// populated by a call to Infos // populated by a call to Infos
@ -74,6 +75,11 @@ func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result {
return r return r
} }
// Mapper returns a copy of the builder's mapper.
func (r *Result) Mapper() *Mapper {
return r.mapper
}
// Err returns one or more errors (via a util.ErrorList) that occurred prior // Err returns one or more errors (via a util.ErrorList) that occurred prior
// to visiting the elements in the visitor. To see all errors including those // to visiting the elements in the visitor. To see all errors including those
// that occur during visitation, invoke Infos(). // that occur during visitation, invoke Infos().

View File

@ -92,13 +92,15 @@ func (r *Selector) Visit(fn VisitorFunc) error {
resourceVersion, _ := accessor.ResourceVersion(list) resourceVersion, _ := accessor.ResourceVersion(list)
nextContinueToken, _ := accessor.Continue(list) nextContinueToken, _ := accessor.Continue(list)
info := &Info{ info := &Info{
Client: r.Client, Client: r.Client,
Mapping: r.Mapping, Mapping: r.Mapping,
Namespace: r.Namespace,
Object: list, Namespace: r.Namespace,
ResourceVersion: resourceVersion, ResourceVersion: resourceVersion,
Object: list,
} }
if err := fn(info, nil); err != nil { if err := fn(info, nil); err != nil {
return err return err
} }

View File

@ -81,10 +81,6 @@ type Info struct {
// Optional, Source is the filename or URL to template file (.json or .yaml), // Optional, Source is the filename or URL to template file (.json or .yaml),
// or stdin to use to handle the resource // or stdin to use to handle the resource
Source string Source string
// Optional, this is the provided object in a versioned type before defaulting
// and conversions into its corresponding internal type. This is useful for
// reflecting on user intent which may be lost after defaulting and conversions.
VersionedObject runtime.Object
// Optional, this is the most recent value returned by the server if available // Optional, this is the most recent value returned by the server if available
Object runtime.Object Object runtime.Object
// Optional, this is the most recent resource version the server knows about for // Optional, this is the most recent resource version the server knows about for
@ -96,17 +92,6 @@ type Info struct {
Export bool Export bool
} }
// NewInfo returns a new info object
func NewInfo(client RESTClient, mapping *meta.RESTMapping, namespace, name string, export bool) *Info {
return &Info{
Client: client,
Mapping: mapping,
Namespace: namespace,
Name: name,
Export: export,
}
}
// Visit implements Visitor // Visit implements Visitor
func (i *Info) Visit(fn VisitorFunc) error { func (i *Info) Visit(fn VisitorFunc) error {
return fn(i, nil) return fn(i, nil)
@ -388,10 +373,7 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error {
if err != nil { if err != nil {
return fn(info, nil) return fn(info, nil)
} }
if errs := runtime.DecodeList(items, struct { if errs := runtime.DecodeList(items, v.Mapper.Decoder); len(errs) > 0 {
runtime.ObjectTyper
runtime.Decoder
}{v.Mapper, v.Mapper.Decoder}); len(errs) > 0 {
return utilerrors.NewAggregate(errs) return utilerrors.NewAggregate(errs)
} }

View File

@ -21,8 +21,24 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
) )
// InterfacesForUnstructuredConversion returns VersionInterfaces suitable for
// dealing with unstructured.Unstructured objects and supports conversion
// from typed objects (provided by parent) to untyped objects.
func InterfacesForUnstructuredConversion(parent VersionInterfacesFunc) VersionInterfacesFunc {
return func(version schema.GroupVersion) (*VersionInterfaces, error) {
if i, err := parent(version); err == nil {
return &VersionInterfaces{
ObjectConvertor: i.ObjectConvertor,
MetadataAccessor: NewAccessor(),
}, nil
}
return InterfacesForUnstructured(version)
}
}
// InterfacesForUnstructured returns VersionInterfaces suitable for // InterfacesForUnstructured returns VersionInterfaces suitable for
// dealing with unstructured.Unstructured objects. // dealing with unstructured.Unstructured objects. It will return errors for
// other conversions.
func InterfacesForUnstructured(schema.GroupVersion) (*VersionInterfaces, error) { func InterfacesForUnstructured(schema.GroupVersion) (*VersionInterfaces, error) {
return &VersionInterfaces{ return &VersionInterfaces{
ObjectConvertor: &unstructured.UnstructuredObjectConverter{}, ObjectConvertor: &unstructured.UnstructuredObjectConverter{},