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.
This commit is contained in:
Clayton Coleman
2017-11-13 22:56:59 -05:00
parent 98e0c69907
commit 0229fd4bd1
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.
func (f *ring2Factory) NewBuilder() *resource.Builder {
clientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.ClientForMapping)
mapper, typer := f.objectMappingFactory.Object()
unstructuredClientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.UnstructuredClientForMapping)
unstructuredMapper, unstructuredTyper, _ := f.objectMappingFactory.UnstructuredObject()
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.

View File

@@ -108,7 +108,9 @@ func (f *ring1Factory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectType
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)
expander, err := NewShortcutExpander(mapper, discoveryClient)
return expander, typer, err

View File

@@ -543,7 +543,16 @@ func TestDiscoveryReplaceAliases(t *testing.T) {
if err != nil {
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 {
replaced := b.ReplaceAliases(test.arg)

View File

@@ -26,9 +26,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
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/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
@@ -46,6 +44,7 @@ const defaultHttpGetAttempts int = 3
// over using the Visitor interface.
type Builder struct {
mapper *Mapper
unstructured *Mapper
categoryExpander categories.CategoryExpander
errs []error
@@ -118,9 +117,10 @@ type resourceTuple struct {
}
// 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{
mapper: &Mapper{typer, mapper, clientMapper, decoder},
mapper: mapper,
unstructured: unstructured,
categoryExpander: categoryExpander,
requireObject: true,
}
@@ -166,23 +166,31 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename
return b
}
// Local wraps the builder's clientMapper in a DisabledClientMapperForMapping
func (b *Builder) Local(mapperFunc ClientMapperFunc) *Builder {
b.mapper.ClientMapper = DisabledClientForMapping{ClientMapper: ClientMapperFunc(mapperFunc)}
// Unstructured updates the builder so that it will request and send unstructured
// objects by default. Calling this method resets Local().
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
}
// Unstructured updates the builder's ClientMapper, RESTMapper,
// ObjectTyper, and codec for working with unstructured api objects
func (b *Builder) Unstructured(mapperFunc ClientMapperFunc, mapper meta.RESTMapper, typer runtime.ObjectTyper) *Builder {
b.mapper.RESTMapper = mapper
b.mapper.ObjectTyper = typer
b.mapper.Decoder = unstructured.UnstructuredJSONScheme
b.mapper.ClientMapper = ClientMapperFunc(mapperFunc)
// Local will avoid asking the server for results.
func (b *Builder) Local() *Builder {
mapper := *b.mapper
mapper.ClientMapper = DisabledClientForMapping{ClientMapper: mapper.ClientMapper}
b.mapper = &mapper
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.
func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder {
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)
}
@@ -814,7 +828,13 @@ func (b *Builder) visitByName() *Result {
visitors := []Visitor{}
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)
}
result.visitor = VisitorList(visitors)
@@ -875,6 +895,7 @@ func (b *Builder) visitByPaths() *Result {
// for further iteration.
func (b *Builder) Do() *Result {
r := b.visitorResult()
r.mapper = b.Mapper()
if r.err != nil {
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) {
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"}})
test := &testVisitor{}
@@ -279,10 +296,11 @@ func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) {
if info.Name != "update-demo-kitten" || info.Namespace != "" || info.Object == nil {
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
if info.VersionedObject == nil || !ok || version.Spec.Replicas != nil {
t.Errorf("unexpected versioned object: %#v", info.VersionedObject)
if obj == nil || !ok || version.Spec.Replicas != nil {
t.Errorf("unexpected versioned object: %#v", obj)
}
}
@@ -303,8 +321,7 @@ func TestNodeBuilder(t *testing.T) {
w.Write([]byte(runtime.EncodeOrDie(corev1Codec, node)))
}()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
NamespaceParam("test").Stream(r, "STDIN")
b := newDefaultBuilder().NamespaceParam("test").Stream(r, "STDIN")
test := &testVisitor{}
@@ -367,7 +384,7 @@ func TestPathBuilderWithMultiple(t *testing.T) {
}
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}}).
NamespaceParam("test").DefaultNamespace()
@@ -426,7 +443,7 @@ func TestPathBuilderWithMultipleInvalid(t *testing.T) {
}
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}}).
NamespaceParam("test").DefaultNamespace()
@@ -441,7 +458,7 @@ func TestPathBuilderWithMultipleInvalid(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"}}).
NamespaceParam("test").DefaultNamespace()
@@ -472,7 +489,7 @@ func TestNamespaceOverride(t *testing.T) {
}))
defer s.Close()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
NamespaceParam("test")
@@ -483,7 +500,7 @@ func TestNamespaceOverride(t *testing.T) {
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}}).
NamespaceParam("test")
@@ -503,7 +520,7 @@ func TestURLBuilder(t *testing.T) {
}))
defer s.Close()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
NamespaceParam("foo")
@@ -532,7 +549,7 @@ func TestURLBuilderRequireNamespace(t *testing.T) {
}))
defer s.Close()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
b := newDefaultBuilder().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
NamespaceParam("test").RequireNamespace()
@@ -547,10 +564,9 @@ func TestURLBuilderRequireNamespace(t *testing.T) {
func TestResourceByName(t *testing.T) {
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]),
}), corev1Codec).
NamespaceParam("test")
})).NamespaceParam("test")
test := &testVisitor{}
singleItemImplied := false
@@ -580,12 +596,12 @@ func TestResourceByName(t *testing.T) {
func TestMultipleResourceByTheSameName(t *testing.T) {
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/baz": runtime.EncodeOrDie(corev1Codec, &pods.Items[1]),
"/namespaces/test/services/foo": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]),
"/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]),
}), corev1Codec).
})).
NamespaceParam("test")
test := &testVisitor{}
@@ -632,11 +648,10 @@ func TestRequestModifier(t *testing.T) {
func TestResourceNames(t *testing.T) {
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/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]),
}), corev1Codec).
NamespaceParam("test")
})).NamespaceParam("test")
test := &testVisitor{}
@@ -660,11 +675,10 @@ func TestResourceNames(t *testing.T) {
func TestResourceNamesWithoutResource(t *testing.T) {
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/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]),
}), corev1Codec).
NamespaceParam("test")
})).NamespaceParam("test")
test := &testVisitor{}
@@ -681,8 +695,7 @@ func TestResourceNamesWithoutResource(t *testing.T) {
}
func TestResourceByNameWithoutRequireObject(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith("", t, map[string]string{}), corev1Codec).
NamespaceParam("test")
b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{})).NamespaceParam("test")
test := &testVisitor{}
singleItemImplied := false
@@ -715,9 +728,9 @@ func TestResourceByNameWithoutRequireObject(t *testing.T) {
func TestResourceByNameAndEmptySelector(t *testing.T) {
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]),
}), corev1Codec).
})).
NamespaceParam("test").
LabelSelectorParam("").
ResourceTypeOrNameArgs(true, "pods", "foo")
@@ -743,10 +756,10 @@ func TestResourceByNameAndEmptySelector(t *testing.T) {
func TestLabelSelector(t *testing.T) {
pods, svc := testData()
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/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
}), corev1Codec).
})).
LabelSelectorParam("a=b").
NamespaceParam("test").
Flatten()
@@ -774,7 +787,7 @@ func TestLabelSelector(t *testing.T) {
}
func TestLabelSelectorRequiresKnownTypes(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
b := newDefaultBuilder().
LabelSelectorParam("a=b").
NamespaceParam("test").
ResourceTypes("unknown")
@@ -787,10 +800,10 @@ func TestLabelSelectorRequiresKnownTypes(t *testing.T) {
func TestFieldSelector(t *testing.T) {
pods, svc := testData()
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/services?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
}), corev1Codec).
})).
FieldSelectorParam("a=b").
NamespaceParam("test").
Flatten()
@@ -818,7 +831,7 @@ func TestFieldSelector(t *testing.T) {
}
func TestFieldSelectorRequiresKnownTypes(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
b := newDefaultBuilder().
FieldSelectorParam("a=b").
NamespaceParam("test").
ResourceTypes("unknown")
@@ -829,7 +842,7 @@ func TestFieldSelectorRequiresKnownTypes(t *testing.T) {
}
func TestSingleResourceType(t *testing.T) {
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
b := newDefaultBuilder().
LabelSelectorParam("a=b").
SingleResourceType().
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"}}),
}
}
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClientWith(k, t, expectedRequests), corev1Codec).
b := newDefaultBuilderWith(fakeClientWith(k, t, expectedRequests)).
NamespaceParam("test").DefaultNamespace().
ResourceTypeOrNameArgs(true, testCase.args...).RequireObject(requireObject)
@@ -930,7 +942,7 @@ func TestResourceTuple(t *testing.T) {
func TestStream(t *testing.T) {
r, pods, rc := streamTestData()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
b := newDefaultBuilder().
NamespaceParam("test").Stream(r, "STDIN").Flatten()
test := &testVisitor{}
@@ -947,7 +959,7 @@ func TestStream(t *testing.T) {
func TestYAMLStream(t *testing.T) {
r, pods, rc := streamYAMLTestData()
b := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
b := newDefaultBuilder().
NamespaceParam("test").Stream(r, "STDIN").Flatten()
test := &testVisitor{}
@@ -964,7 +976,7 @@ func TestYAMLStream(t *testing.T) {
func TestMultipleObject(t *testing.T) {
r, pods, svc := streamTestData()
obj, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
obj, err := newDefaultBuilder().
NamespaceParam("test").Stream(r, "STDIN").Flatten().
Do().Object()
@@ -986,7 +998,7 @@ func TestMultipleObject(t *testing.T) {
func TestContinueOnErrorVisitor(t *testing.T) {
r, _, _ := streamTestData()
req := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
req := newDefaultBuilder().
ContinueOnError().
NamespaceParam("test").Stream(r, "STDIN").Flatten().
Do()
@@ -1015,7 +1027,7 @@ func TestContinueOnErrorVisitor(t *testing.T) {
}
func TestSingleItemImpliedObject(t *testing.T) {
obj, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
obj, err := newDefaultBuilder().
NamespaceParam("test").DefaultNamespace().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}}).
Flatten().
@@ -1035,7 +1047,7 @@ func TestSingleItemImpliedObject(t *testing.T) {
}
func TestSingleItemImpliedObjectNoExtension(t *testing.T) {
obj, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
obj, err := newDefaultBuilder().
NamespaceParam("test").DefaultNamespace().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/pod"}}).
Flatten().
@@ -1057,7 +1069,7 @@ func TestSingleItemImpliedObjectNoExtension(t *testing.T) {
func TestSingleItemImpliedRootScopedObject(t *testing.T) {
node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test"}, Spec: v1.NodeSpec{ExternalID: "test"}}
r := streamTestObject(node)
infos, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
infos, err := newDefaultBuilder().
NamespaceParam("test").DefaultNamespace().
Stream(r, "STDIN").
Flatten().
@@ -1082,9 +1094,9 @@ func TestSingleItemImpliedRootScopedObject(t *testing.T) {
func TestListObject(t *testing.T) {
pods, _ := testData()
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),
}), corev1Codec).
})).
LabelSelectorParam("a=b").
NamespaceParam("test").
ResourceTypeOrNameArgs(true, "pods").
@@ -1115,10 +1127,10 @@ func TestListObject(t *testing.T) {
func TestListObjectWithDifferentVersions(t *testing.T) {
pods, svc := testData()
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/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
}), corev1Codec).
})).
LabelSelectorParam("a=b").
NamespaceParam("test").
ResourceTypeOrNameArgs(true, "pods,services").
@@ -1141,12 +1153,12 @@ func TestListObjectWithDifferentVersions(t *testing.T) {
func TestWatch(t *testing.T) {
_, 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{
Type: watch.Added,
Object: &svc.Items[0],
}),
}), corev1Codec).
})).
NamespaceParam("test").DefaultNamespace().
FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../../examples/guestbook/redis-master-service.yaml"}}).Flatten().
Do().Watch("12")
@@ -1173,7 +1185,7 @@ func TestWatch(t *testing.T) {
}
func TestWatchMultipleError(t *testing.T) {
_, err := NewBuilder(restmapper, categories.LegacyCategoryExpander, scheme.Scheme, fakeClient(), corev1Codec).
_, err := newDefaultBuilder().
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().
@@ -1196,11 +1208,11 @@ func TestLatest(t *testing.T) {
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/bar": runtime.EncodeOrDie(corev1Codec, newPod2),
"/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, newSvc),
}), corev1Codec).
})).
NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest()
test := &testVisitor{}
@@ -1232,7 +1244,7 @@ func TestReceiveMultipleErrors(t *testing.T) {
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").
ContinueOnError()

View File

@@ -25,17 +25,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
// 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
}
// Mapper is a convenience struct for holding references to the three interfaces
// Mapper is a convenience struct for holding references to the interfaces
// needed to create Info for arbitrary objects.
type Mapper struct {
runtime.ObjectTyper
@@ -44,17 +34,27 @@ type Mapper struct {
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
// 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.
func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
versions := &runtime.VersionedObjects{}
_, gvk, err := m.Decode(data, nil, versions)
obj, gvk, err := m.Decode(data, nil, nil)
if err != nil {
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)
if err != nil {
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)
return &Info{
Mapping: mapping,
Client: client,
Client: client,
Mapping: mapping,
Source: source,
Namespace: namespace,
Name: name,
Source: source,
VersionedObject: versioned,
Object: obj,
ResourceVersion: resourceVersion,
Object: obj,
}, nil
}
@@ -99,6 +100,7 @@ func (m *Mapper) InfoForObject(obj runtime.Object, preferredGVKs []schema.GroupV
if err != nil {
return nil, fmt.Errorf("unable to recognize %v: %v", groupVersionKind, err)
}
client, err := m.ClientForMapping(mapping)
if err != nil {
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)
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)
return &Info{
Mapping: mapping,
Client: client,
Namespace: namespace,
Name: name,
Client: client,
Mapping: mapping,
Object: obj,
Namespace: namespace,
Name: name,
ResourceVersion: resourceVersion,
Object: obj,
}, nil
}
@@ -152,3 +155,83 @@ func preferredObjectKind(possibilities []schema.GroupVersionKind, preferences []
// Just pick the first
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
targetsSingleItems bool
mapper *Mapper
ignoreErrors []utilerrors.Matcher
// populated by a call to Infos
@@ -74,6 +75,11 @@ func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result {
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
// to visiting the elements in the visitor. To see all errors including those
// that occur during visitation, invoke Infos().

View File

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

View File

@@ -81,10 +81,6 @@ type Info struct {
// Optional, Source is the filename or URL to template file (.json or .yaml),
// or stdin to use to handle the resource
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
Object runtime.Object
// Optional, this is the most recent resource version the server knows about for
@@ -96,17 +92,6 @@ type Info struct {
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
func (i *Info) Visit(fn VisitorFunc) error {
return fn(i, nil)
@@ -388,10 +373,7 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error {
if err != nil {
return fn(info, nil)
}
if errs := runtime.DecodeList(items, struct {
runtime.ObjectTyper
runtime.Decoder
}{v.Mapper, v.Mapper.Decoder}); len(errs) > 0 {
if errs := runtime.DecodeList(items, v.Mapper.Decoder); len(errs) > 0 {
return utilerrors.NewAggregate(errs)
}

View File

@@ -21,8 +21,24 @@ import (
"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
// dealing with unstructured.Unstructured objects.
// dealing with unstructured.Unstructured objects. It will return errors for
// other conversions.
func InterfacesForUnstructured(schema.GroupVersion) (*VersionInterfaces, error) {
return &VersionInterfaces{
ObjectConvertor: &unstructured.UnstructuredObjectConverter{},