mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-08 20:50:24 +00:00
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:
@@ -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{},
|
||||
|
Reference in New Issue
Block a user