mirror of https://github.com/k3s-io/k3s
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
parent
98e0c69907
commit
0229fd4bd1
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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().
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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{},
|
||||
|
|
Loading…
Reference in New Issue