diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index cce81edafc9..c396f8df00f 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -281,6 +281,27 @@ runTests() { fi stop-proxy + ######################### + # RESTMapper evaluation # + ######################### + + kube::log::status "Testing RESTMapper" + + RESTMAPPER_ERROR_FILE="${KUBE_TEMP}/restmapper-error" + + ### Non-existent resource type should give a recognizeable error + # Pre-condition: None + # Command + kubectl get "${kube_flags[@]}" unknownresourcetype 2>${RESTMAPPER_ERROR_FILE} || true + if grep -q "the server doesn't have a resource type" "${RESTMAPPER_ERROR_FILE}"; then + kube::log::status "\"kubectl get unknownresourcetype\" returns error as expected: $(cat ${RESTMAPPER_ERROR_FILE})" + else + kube::log::status "\"kubectl get unknownresourcetype\" returns unexpected error or non-error: $(cat ${RESTMAPPER_ERROR_FILE})" + exit 1 + fi + rm "${RESTMAPPER_ERROR_FILE}" + # Post-condition: None + ########################### # POD creation / deletion # ########################### diff --git a/pkg/api/meta/errors.go b/pkg/api/meta/errors.go new file mode 100644 index 00000000000..2b89bcb8ac3 --- /dev/null +++ b/pkg/api/meta/errors.go @@ -0,0 +1,72 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package meta + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// AmbiguousResourceError is returned if the RESTMapper finds multiple matches for a resource +type AmbiguousResourceError struct { + PartialResource unversioned.GroupVersionResource + + MatchingResources []unversioned.GroupVersionResource + MatchingKinds []unversioned.GroupVersionKind +} + +func (e *AmbiguousResourceError) Error() string { + switch { + case len(e.MatchingKinds) > 0 && len(e.MatchingResources) > 0: + return fmt.Sprintf("%v matches multiple resources %v and kinds %v", e.PartialResource, e.MatchingResources, e.MatchingKinds) + case len(e.MatchingKinds) > 0: + return fmt.Sprintf("%v matches multiple kinds %v", e.PartialResource, e.MatchingKinds) + case len(e.MatchingResources) > 0: + return fmt.Sprintf("%v matches multiple resources %v", e.PartialResource, e.MatchingResources) + + } + + return fmt.Sprintf("%v matches multiple resources or kinds", e.PartialResource) +} + +func IsAmbiguousResourceError(err error) bool { + if err == nil { + return false + } + + _, ok := err.(*AmbiguousResourceError) + return ok +} + +// NoResourceMatchError is returned if the RESTMapper can't find any match for a resource +type NoResourceMatchError struct { + PartialResource unversioned.GroupVersionResource +} + +func (e *NoResourceMatchError) Error() string { + return fmt.Sprintf("no matches for %v", e.PartialResource) +} + +func IsNoResourceMatchError(err error) bool { + if err == nil { + return false + } + + _, ok := err.(*NoResourceMatchError) + return ok +} diff --git a/pkg/api/meta/multirestmapper.go b/pkg/api/meta/multirestmapper.go new file mode 100644 index 00000000000..eb63c018781 --- /dev/null +++ b/pkg/api/meta/multirestmapper.go @@ -0,0 +1,131 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package meta + +import ( + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// MultiRESTMapper is a wrapper for multiple RESTMappers. +type MultiRESTMapper []RESTMapper + +func (m MultiRESTMapper) String() string { + nested := []string{} + for _, t := range m { + currString := fmt.Sprintf("%v", t) + splitStrings := strings.Split(currString, "\n") + nested = append(nested, strings.Join(splitStrings, "\n\t")) + } + + return fmt.Sprintf("MultiRESTMapper{\n\t%s\n}", strings.Join(nested, "\n\t")) +} + +// ResourceSingularizer converts a REST resource name from plural to singular (e.g., from pods to pod) +// This implementation supports multiple REST schemas and return the first match. +func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + for _, t := range m { + singular, err = t.ResourceSingularizer(resource) + if err == nil { + return + } + } + return +} + +func (m MultiRESTMapper) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { + for _, t := range m { + gvrs, err := t.ResourcesFor(resource) + // ignore "no match" errors, but any other error percolates back up + if !IsNoResourceMatchError(err) { + return gvrs, err + } + } + return nil, &NoResourceMatchError{PartialResource: resource} +} + +// KindsFor provides the Kind mappings for the REST resources. This implementation supports multiple REST schemas and returns +// the first match. +func (m MultiRESTMapper) KindsFor(resource unversioned.GroupVersionResource) (gvk []unversioned.GroupVersionKind, err error) { + for _, t := range m { + gvks, err := t.KindsFor(resource) + // ignore "no match" errors, but any other error percolates back up + if !IsNoResourceMatchError(err) { + return gvks, err + } + } + return nil, &NoResourceMatchError{PartialResource: resource} +} + +func (m MultiRESTMapper) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) { + for _, t := range m { + gvr, err := t.ResourceFor(resource) + // ignore "no match" errors, but any other error percolates back up + if !IsNoResourceMatchError(err) { + return gvr, err + } + } + return unversioned.GroupVersionResource{}, &NoResourceMatchError{PartialResource: resource} +} + +// KindsFor provides the Kind mapping for the REST resources. This implementation supports multiple REST schemas and returns +// the first match. +func (m MultiRESTMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { + for _, t := range m { + gvk, err := t.KindFor(resource) + // ignore "no match" errors, but any other error percolates back up + if !IsNoResourceMatchError(err) { + return gvk, err + } + } + return unversioned.GroupVersionKind{}, &NoResourceMatchError{PartialResource: resource} +} + +// RESTMapping provides the REST mapping for the resource based on the +// kind and version. This implementation supports multiple REST schemas and +// return the first match. +func (m MultiRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (mapping *RESTMapping, err error) { + for _, t := range m { + mapping, err = t.RESTMapping(gk, versions...) + if err == nil { + return + } + } + return +} + +// AliasesForResource finds the first alias response for the provided mappers. +func (m MultiRESTMapper) AliasesForResource(alias string) (aliases []string, ok bool) { + for _, t := range m { + if aliases, ok = t.AliasesForResource(alias); ok { + return + } + } + return nil, false +} + +// ResourceIsValid takes a string (either group/kind or kind) and checks if it's a valid resource +func (m MultiRESTMapper) ResourceIsValid(resource unversioned.GroupVersionResource) bool { + for _, t := range m { + if t.ResourceIsValid(resource) { + return true + } + } + return false +} diff --git a/pkg/api/meta/multirestmapper_test.go b/pkg/api/meta/multirestmapper_test.go new file mode 100644 index 00000000000..c4d54311589 --- /dev/null +++ b/pkg/api/meta/multirestmapper_test.go @@ -0,0 +1,238 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package meta + +import ( + "errors" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +func TestMultiRESTMapperResourceForErrorHandling(t *testing.T) { + tcs := []struct { + name string + + mapper MultiRESTMapper + input unversioned.GroupVersionResource + result unversioned.GroupVersionResource + err error + }{ + { + name: "empty", + mapper: MultiRESTMapper{}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: unversioned.GroupVersionResource{}, + err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "ignore not found", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: unversioned.GroupVersionResource{}, + err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{resourceFor: unversioned.GroupVersionResource{Resource: "unused"}}}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: unversioned.GroupVersionResource{}, + err: errors.New("fail on this"), + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.ResourceFor(tc.input) + if e, a := tc.result, actualResult; e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.err.Error(), actualErr.Error(); e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + } +} + +func TestMultiRESTMapperResourcesForErrorHandling(t *testing.T) { + tcs := []struct { + name string + + mapper MultiRESTMapper + input unversioned.GroupVersionResource + result []unversioned.GroupVersionResource + err error + }{ + { + name: "empty", + mapper: MultiRESTMapper{}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: nil, + err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "ignore not found", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: nil, + err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{{Resource: "unused"}}}}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: nil, + err: errors.New("fail on this"), + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.ResourcesFor(tc.input) + if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.err.Error(), actualErr.Error(); e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + } +} + +func TestMultiRESTMapperKindsForErrorHandling(t *testing.T) { + tcs := []struct { + name string + + mapper MultiRESTMapper + input unversioned.GroupVersionResource + result []unversioned.GroupVersionKind + err error + }{ + { + name: "empty", + mapper: MultiRESTMapper{}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: nil, + err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "ignore not found", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: nil, + err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{{Kind: "unused"}}}}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: nil, + err: errors.New("fail on this"), + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.KindsFor(tc.input) + if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.err.Error(), actualErr.Error(); e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + } +} + +func TestMultiRESTMapperKindForErrorHandling(t *testing.T) { + tcs := []struct { + name string + + mapper MultiRESTMapper + input unversioned.GroupVersionResource + result unversioned.GroupVersionKind + err error + }{ + { + name: "empty", + mapper: MultiRESTMapper{}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: unversioned.GroupVersionKind{}, + err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "ignore not found", + mapper: MultiRESTMapper{fixedRESTMapper{err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "IGNORE_THIS"}}}}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: unversioned.GroupVersionKind{}, + err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "foo"}}, + }, + { + name: "accept first failure", + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{kindFor: unversioned.GroupVersionKind{Kind: "unused"}}}, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: unversioned.GroupVersionKind{}, + err: errors.New("fail on this"), + }, + } + + for _, tc := range tcs { + actualResult, actualErr := tc.mapper.KindFor(tc.input) + if e, a := tc.result, actualResult; e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.err.Error(), actualErr.Error(); e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + } +} + +type fixedRESTMapper struct { + resourcesFor []unversioned.GroupVersionResource + kindsFor []unversioned.GroupVersionKind + resourceFor unversioned.GroupVersionResource + kindFor unversioned.GroupVersionKind + + err error +} + +func (m fixedRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + return "", m.err +} + +func (m fixedRESTMapper) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { + return m.resourcesFor, m.err +} + +func (m fixedRESTMapper) KindsFor(resource unversioned.GroupVersionResource) (gvk []unversioned.GroupVersionKind, err error) { + return m.kindsFor, m.err +} + +func (m fixedRESTMapper) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) { + return m.resourceFor, m.err +} + +func (m fixedRESTMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { + return m.kindFor, m.err +} + +func (m fixedRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (mapping *RESTMapping, err error) { + return nil, m.err +} + +func (m fixedRESTMapper) AliasesForResource(alias string) (aliases []string, ok bool) { + return nil, false +} + +func (m fixedRESTMapper) ResourceIsValid(resource unversioned.GroupVersionResource) bool { + return false +} diff --git a/pkg/api/meta/restmapper.go b/pkg/api/meta/restmapper.go index 3fb185d6f59..f4158ec4775 100644 --- a/pkg/api/meta/restmapper.go +++ b/pkg/api/meta/restmapper.go @@ -243,7 +243,7 @@ func (m *DefaultRESTMapper) ResourcesFor(resource unversioned.GroupVersionResour } if len(ret) == 0 { - return nil, fmt.Errorf("no resource %v has been defined; known resources: %v", resource, m.pluralToSingular) + return nil, &NoResourceMatchError{PartialResource: resource} } sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions}) @@ -259,7 +259,7 @@ func (m *DefaultRESTMapper) ResourceFor(resource unversioned.GroupVersionResourc return resources[0], nil } - return unversioned.GroupVersionResource{}, fmt.Errorf("%v is ambiguous, got: %v", resource, resources) + return unversioned.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources} } func (m *DefaultRESTMapper) KindsFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) { @@ -309,7 +309,7 @@ func (m *DefaultRESTMapper) KindsFor(input unversioned.GroupVersionResource) ([] } if len(ret) == 0 { - return nil, fmt.Errorf("no kind %v has been defined; known resources: %v", resource, m.pluralToSingular) + return nil, &NoResourceMatchError{PartialResource: input} } sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions}) @@ -340,7 +340,7 @@ func (m *DefaultRESTMapper) KindFor(resource unversioned.GroupVersionResource) ( return oneKindPerGroup[0], nil } - return unversioned.GroupVersionKind{}, fmt.Errorf("%v is ambiguous, got: %v", resource, kinds) + return unversioned.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds} } type kindByPreferredGroupVersion struct { @@ -519,106 +519,3 @@ func (m *DefaultRESTMapper) ResourceIsValid(resource unversioned.GroupVersionRes _, err := m.KindFor(resource) return err == nil } - -// MultiRESTMapper is a wrapper for multiple RESTMappers. -type MultiRESTMapper []RESTMapper - -func (m MultiRESTMapper) String() string { - nested := []string{} - for _, t := range m { - currString := fmt.Sprintf("%v", t) - splitStrings := strings.Split(currString, "\n") - nested = append(nested, strings.Join(splitStrings, "\n\t")) - } - - return fmt.Sprintf("MultiRESTMapper{\n\t%s\n}", strings.Join(nested, "\n\t")) -} - -// ResourceSingularizer converts a REST resource name from plural to singular (e.g., from pods to pod) -// This implementation supports multiple REST schemas and return the first match. -func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { - for _, t := range m { - singular, err = t.ResourceSingularizer(resource) - if err == nil { - return - } - } - return -} - -func (m MultiRESTMapper) ResourcesFor(resource unversioned.GroupVersionResource) (gvk []unversioned.GroupVersionResource, err error) { - for _, t := range m { - gvk, err = t.ResourcesFor(resource) - if err == nil { - return - } - } - return -} - -// KindsFor provides the Kind mappings for the REST resources. This implementation supports multiple REST schemas and returns -// the first match. -func (m MultiRESTMapper) KindsFor(resource unversioned.GroupVersionResource) (gvk []unversioned.GroupVersionKind, err error) { - for _, t := range m { - gvk, err = t.KindsFor(resource) - if err == nil { - return - } - } - return -} - -func (m MultiRESTMapper) ResourceFor(resource unversioned.GroupVersionResource) (gvk unversioned.GroupVersionResource, err error) { - for _, t := range m { - gvk, err = t.ResourceFor(resource) - if err == nil { - return - } - } - return -} - -// KindsFor provides the Kind mapping for the REST resources. This implementation supports multiple REST schemas and returns -// the first match. -func (m MultiRESTMapper) KindFor(resource unversioned.GroupVersionResource) (gvk unversioned.GroupVersionKind, err error) { - for _, t := range m { - gvk, err = t.KindFor(resource) - if err == nil { - return - } - } - return -} - -// RESTMapping provides the REST mapping for the resource based on the -// kind and version. This implementation supports multiple REST schemas and -// return the first match. -func (m MultiRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (mapping *RESTMapping, err error) { - for _, t := range m { - mapping, err = t.RESTMapping(gk, versions...) - if err == nil { - return - } - } - return -} - -// AliasesForResource finds the first alias response for the provided mappers. -func (m MultiRESTMapper) AliasesForResource(alias string) (aliases []string, ok bool) { - for _, t := range m { - if aliases, ok = t.AliasesForResource(alias); ok { - return - } - } - return nil, false -} - -// ResourceIsValid takes a string (either group/kind or kind) and checks if it's a valid resource -func (m MultiRESTMapper) ResourceIsValid(resource unversioned.GroupVersionResource) bool { - for _, t := range m { - if t.ResourceIsValid(resource) { - return true - } - } - return false -} diff --git a/pkg/api/meta/restmapper_test.go b/pkg/api/meta/restmapper_test.go index 0f7b0fcd9d6..a41ae5d21de 100644 --- a/pkg/api/meta/restmapper_test.go +++ b/pkg/api/meta/restmapper_test.go @@ -148,7 +148,7 @@ func TestRESTMapperKindsFor(t *testing.T) { {Group: "second-group", Version: "first-version", Kind: "my-kind"}, {Group: "first-group", Version: "first-version", Kind: "my-kind"}, }, - ExpectedKindErr: "is ambiguous", + ExpectedKindErr: " matches multiple kinds ", }, { @@ -188,7 +188,7 @@ func TestRESTMapperKindsFor(t *testing.T) { {Group: "first-group", Version: "first-version", Kind: "my-kind"}, {Group: "second-group", Version: "first-version", Kind: "my-kind"}, }, - ExpectedKindErr: "is ambiguous", + ExpectedKindErr: " matches multiple kinds ", }, } for _, testCase := range testCases { @@ -260,7 +260,7 @@ func TestRESTMapperResourcesFor(t *testing.T) { {Group: "second-group", Version: "first-version", Resource: "my-kinds"}, {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, }, - ExpectedResourceErr: "is ambiguous", + ExpectedResourceErr: " matches multiple resources ", }, { @@ -300,7 +300,7 @@ func TestRESTMapperResourcesFor(t *testing.T) { {Group: "first-group", Version: "first-version", Resource: "my-kinds"}, {Group: "second-group", Version: "first-version", Resource: "my-kinds"}, }, - ExpectedResourceErr: "is ambiguous", + ExpectedResourceErr: " matches multiple resources ", }, } for _, testCase := range testCases { diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index f4364964fc7..6b2ef69dc20 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -30,6 +30,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/kubectl" @@ -118,6 +119,22 @@ func checkErr(err error, handleErr func(string)) { handleErr(MultilineError(prefix, errs)) } + if meta.IsNoResourceMatchError(err) { + noMatch := err.(*meta.NoResourceMatchError) + + switch { + case len(noMatch.PartialResource.Group) > 0 && len(noMatch.PartialResource.Version) > 0: + handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q and version %q", noMatch.PartialResource.Resource, noMatch.PartialResource.Group, noMatch.PartialResource.Version)) + case len(noMatch.PartialResource.Group) > 0: + handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q", noMatch.PartialResource.Resource, noMatch.PartialResource.Group)) + case len(noMatch.PartialResource.Version) > 0: + handleErr(fmt.Sprintf("the server doesn't have a resource type %q in version %q", noMatch.PartialResource.Resource, noMatch.PartialResource.Version)) + default: + handleErr(fmt.Sprintf("the server doesn't have a resource type %q", noMatch.PartialResource.Resource)) + } + return + } + // handle multiline errors if clientcmd.IsConfigurationInvalid(err) { handleErr(MultilineError("Error in configuration: ", err)) diff --git a/pkg/kubectl/cmd/util/helpers_test.go b/pkg/kubectl/cmd/util/helpers_test.go index 598c3e961e2..10422e676b7 100644 --- a/pkg/kubectl/cmd/util/helpers_test.go +++ b/pkg/kubectl/cmd/util/helpers_test.go @@ -28,8 +28,10 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/validation/field" ) @@ -302,6 +304,43 @@ func TestCheckInvalidErr(t *testing.T) { } } +func TestCheckNoResourceMatchError(t *testing.T) { + tests := []struct { + err error + expected string + }{ + { + &meta.NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "foo"}}, + `the server doesn't have a resource type "foo"`, + }, + { + &meta.NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Version: "theversion", Resource: "foo"}}, + `the server doesn't have a resource type "foo" in version "theversion"`, + }, + { + &meta.NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Group: "thegroup", Version: "theversion", Resource: "foo"}}, + `the server doesn't have a resource type "foo" in group "thegroup" and version "theversion"`, + }, + { + &meta.NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Group: "thegroup", Resource: "foo"}}, + `the server doesn't have a resource type "foo" in group "thegroup"`, + }, + } + + var errReturned string + errHandle := func(err string) { + errReturned = err + } + + for _, test := range tests { + checkErr(test.err, errHandle) + + if errReturned != test.expected { + t.Fatalf("Got: %s, expected: %s", errReturned, test.expected) + } + } +} + func TestDumpReaderToFile(t *testing.T) { testString := "TEST STRING" tempFile, err := ioutil.TempFile("", "hlpers_test_dump_")