diff --git a/pkg/client/apiutil/errors.go b/pkg/client/apiutil/errors.go new file mode 100644 index 0000000000..c216c49d2a --- /dev/null +++ b/pkg/client/apiutil/errors.go @@ -0,0 +1,54 @@ +/* +Copyright 2023 The Kubernetes Authors. + +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 apiutil + +import ( + "fmt" + "sort" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// ErrResourceDiscoveryFailed is returned if the RESTMapper cannot discover supported resources for some GroupVersions. +// It wraps the errors encountered, except "NotFound" errors are replaced with meta.NoResourceMatchError, for +// backwards compatibility with code that uses meta.IsNoMatchError() to check for unsupported APIs. +type ErrResourceDiscoveryFailed map[schema.GroupVersion]error + +// Error implements the error interface. +func (e *ErrResourceDiscoveryFailed) Error() string { + subErrors := []string{} + for k, v := range *e { + subErrors = append(subErrors, fmt.Sprintf("%s: %v", k, v)) + } + sort.Strings(subErrors) + return fmt.Sprintf("unable to retrieve the complete list of server APIs: %s", strings.Join(subErrors, ", ")) +} + +func (e *ErrResourceDiscoveryFailed) Unwrap() []error { + subErrors := []error{} + for gv, err := range *e { + if apierrors.IsNotFound(err) { + err = &meta.NoResourceMatchError{PartialResource: gv.WithResource("")} + } + subErrors = append(subErrors, err) + } + return subErrors +} diff --git a/pkg/client/apiutil/restmapper.go b/pkg/client/apiutil/restmapper.go index e0ff72dc13..d5e03b2b19 100644 --- a/pkg/client/apiutil/restmapper.go +++ b/pkg/client/apiutil/restmapper.go @@ -286,7 +286,8 @@ func (m *mapper) fetchGroupVersionResources(groupName string, versions ...string } if len(failedGroups) > 0 { - return nil, &discovery.ErrGroupDiscoveryFailed{Groups: failedGroups} + err := ErrResourceDiscoveryFailed(failedGroups) + return nil, &err } return groupVersionResources, nil diff --git a/pkg/client/apiutil/restmapper_test.go b/pkg/client/apiutil/restmapper_test.go index 265313be7e..bee63bf240 100644 --- a/pkg/client/apiutil/restmapper_test.go +++ b/pkg/client/apiutil/restmapper_test.go @@ -21,6 +21,8 @@ import ( "net/http" "testing" + "k8s.io/apimachinery/pkg/api/meta" + _ "github.com/onsi/ginkgo/v2" gmg "github.com/onsi/gomega" @@ -303,26 +305,32 @@ func TestLazyRestMapperProvider(t *testing.T) { _, err = lazyRestMapper.RESTMapping(schema.GroupKind{Group: "INVALID1"}, "v1") g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(1)) _, err = lazyRestMapper.RESTMappings(schema.GroupKind{Group: "INVALID2"}, "v1") g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(2)) _, err = lazyRestMapper.KindFor(schema.GroupVersionResource{Group: "INVALID3", Version: "v1"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(3)) _, err = lazyRestMapper.KindsFor(schema.GroupVersionResource{Group: "INVALID4", Version: "v1"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(4)) _, err = lazyRestMapper.ResourceFor(schema.GroupVersionResource{Group: "INVALID5", Version: "v1"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(5)) _, err = lazyRestMapper.ResourcesFor(schema.GroupVersionResource{Group: "INVALID6", Version: "v1"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(6)) }) @@ -342,26 +350,32 @@ func TestLazyRestMapperProvider(t *testing.T) { _, err = lazyRestMapper.RESTMapping(schema.GroupKind{Group: "apps", Kind: "INVALID"}, "v1") g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(1)) _, err = lazyRestMapper.RESTMappings(schema.GroupKind{Group: "", Kind: "INVALID"}, "v1") g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(2)) _, err = lazyRestMapper.KindFor(schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "INVALID"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(3)) _, err = lazyRestMapper.KindsFor(schema.GroupVersionResource{Group: "authentication.k8s.io", Version: "v1", Resource: "INVALID"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(4)) _, err = lazyRestMapper.ResourceFor(schema.GroupVersionResource{Group: "scheduling.k8s.io", Version: "v1", Resource: "INVALID"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(5)) _, err = lazyRestMapper.ResourcesFor(schema.GroupVersionResource{Group: "policy", Version: "v1", Resource: "INVALID"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(6)) }) @@ -381,26 +395,32 @@ func TestLazyRestMapperProvider(t *testing.T) { _, err = lazyRestMapper.RESTMapping(schema.GroupKind{Group: "apps", Kind: "deployment"}, "INVALID") g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(1)) _, err = lazyRestMapper.RESTMappings(schema.GroupKind{Group: "", Kind: "pod"}, "INVALID") g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(2)) _, err = lazyRestMapper.KindFor(schema.GroupVersionResource{Group: "networking.k8s.io", Version: "INVALID", Resource: "ingresses"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(3)) _, err = lazyRestMapper.KindsFor(schema.GroupVersionResource{Group: "authentication.k8s.io", Version: "INVALID", Resource: "tokenreviews"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(4)) _, err = lazyRestMapper.ResourceFor(schema.GroupVersionResource{Group: "scheduling.k8s.io", Version: "INVALID", Resource: "priorityclasses"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(5)) _, err = lazyRestMapper.ResourcesFor(schema.GroupVersionResource{Group: "policy", Version: "INVALID", Resource: "poddisruptionbudgets"}) g.Expect(err).To(gmg.HaveOccurred()) + g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue()) g.Expect(crt.GetRequestCount()).To(gmg.Equal(6)) })