Skip to content

Commit 7f316f1

Browse files
ary1992timebertt
andauthored
⚠️ RESTMapper: don't treat non-existing GroupVersions as errors (#2571)
* Explicitly test for `No*MatchErrors` * RESTMapper: don't treat non-existing GroupVersions as errors --------- Co-authored-by: Tim Ebert <[email protected]>
1 parent 5e8d572 commit 7f316f1

File tree

2 files changed

+81
-15
lines changed

2 files changed

+81
-15
lines changed

pkg/client/apiutil/restmapper.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"net/http"
2222
"sync"
2323

24+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2425
"k8s.io/apimachinery/pkg/api/meta"
2526
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -166,8 +167,10 @@ func (m *mapper) addKnownGroupAndReload(groupName string, versions ...string) er
166167
if err != nil {
167168
return err
168169
}
169-
for _, version := range apiGroup.Versions {
170-
versions = append(versions, version.Version)
170+
if apiGroup != nil {
171+
for _, version := range apiGroup.Versions {
172+
versions = append(versions, version.Version)
173+
}
171174
}
172175
}
173176

@@ -254,17 +257,12 @@ func (m *mapper) findAPIGroupByName(groupName string) (*metav1.APIGroup, error)
254257
m.mu.Unlock()
255258

256259
// Looking in the cache again.
257-
{
258-
m.mu.RLock()
259-
group, ok := m.apiGroups[groupName]
260-
m.mu.RUnlock()
261-
if ok {
262-
return group, nil
263-
}
264-
}
260+
m.mu.RLock()
261+
defer m.mu.RUnlock()
265262

266-
// If there is still nothing, return an error.
267-
return nil, fmt.Errorf("failed to find API group %q", groupName)
263+
// Don't return an error here if the API group is not present.
264+
// The reloaded RESTMapper will take care of returning a NoMatchError.
265+
return m.apiGroups[groupName], nil
268266
}
269267

270268
// fetchGroupVersionResources fetches the resources for the specified group and its versions.
@@ -276,7 +274,7 @@ func (m *mapper) fetchGroupVersionResources(groupName string, versions ...string
276274
groupVersion := schema.GroupVersion{Group: groupName, Version: version}
277275

278276
apiResourceList, err := m.client.ServerResourcesForGroupVersion(groupVersion.String())
279-
if err != nil {
277+
if err != nil && !apierrors.IsNotFound(err) {
280278
failedGroups[groupVersion] = err
281279
}
282280
if apiResourceList != nil {

pkg/client/apiutil/restmapper_test.go

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,22 @@ package apiutil_test
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"net/http"
2223
"testing"
2324

24-
"k8s.io/apimachinery/pkg/api/meta"
25-
2625
_ "github.com/onsi/ginkgo/v2"
2726
gmg "github.com/onsi/gomega"
27+
"github.com/onsi/gomega/format"
28+
gomegatypes "github.com/onsi/gomega/types"
2829

2930
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
31+
"k8s.io/apimachinery/pkg/api/meta"
3032
"k8s.io/apimachinery/pkg/runtime/schema"
3133
"k8s.io/apimachinery/pkg/types"
3234
"k8s.io/client-go/kubernetes/scheme"
3335
"k8s.io/client-go/rest"
36+
3437
"sigs.k8s.io/controller-runtime/pkg/client"
3538
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
3639
"sigs.k8s.io/controller-runtime/pkg/envtest"
@@ -303,6 +306,10 @@ func TestLazyRestMapperProvider(t *testing.T) {
303306
lazyRestMapper, err := apiutil.NewDynamicRESTMapper(restCfg, httpClient)
304307
g.Expect(err).NotTo(gmg.HaveOccurred())
305308

309+
// A version is specified but the group doesn't exist.
310+
// For each group, we expect 1 call to the version-specific discovery endpoint:
311+
// #1: GET https://host/apis/<group>/<version>
312+
306313
_, err = lazyRestMapper.RESTMapping(schema.GroupKind{Group: "INVALID1"}, "v1")
307314
g.Expect(err).To(gmg.HaveOccurred())
308315
g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue())
@@ -332,6 +339,35 @@ func TestLazyRestMapperProvider(t *testing.T) {
332339
g.Expect(err).To(gmg.HaveOccurred())
333340
g.Expect(meta.IsNoMatchError(err)).To(gmg.BeTrue())
334341
g.Expect(crt.GetRequestCount()).To(gmg.Equal(6))
342+
343+
// No version is specified but the group doesn't exist.
344+
// For each group, we expect 2 calls to discover all group versions:
345+
// #1: GET https://host/api
346+
// #2: GET https://host/apis
347+
348+
_, err = lazyRestMapper.RESTMapping(schema.GroupKind{Group: "INVALID7"})
349+
g.Expect(err).To(beNoMatchError())
350+
g.Expect(crt.GetRequestCount()).To(gmg.Equal(8))
351+
352+
_, err = lazyRestMapper.RESTMappings(schema.GroupKind{Group: "INVALID8"})
353+
g.Expect(err).To(beNoMatchError())
354+
g.Expect(crt.GetRequestCount()).To(gmg.Equal(10))
355+
356+
_, err = lazyRestMapper.KindFor(schema.GroupVersionResource{Group: "INVALID9"})
357+
g.Expect(err).To(beNoMatchError())
358+
g.Expect(crt.GetRequestCount()).To(gmg.Equal(12))
359+
360+
_, err = lazyRestMapper.KindsFor(schema.GroupVersionResource{Group: "INVALID10"})
361+
g.Expect(err).To(beNoMatchError())
362+
g.Expect(crt.GetRequestCount()).To(gmg.Equal(14))
363+
364+
_, err = lazyRestMapper.ResourceFor(schema.GroupVersionResource{Group: "INVALID11"})
365+
g.Expect(err).To(beNoMatchError())
366+
g.Expect(crt.GetRequestCount()).To(gmg.Equal(16))
367+
368+
_, err = lazyRestMapper.ResourcesFor(schema.GroupVersionResource{Group: "INVALID12"})
369+
g.Expect(err).To(beNoMatchError())
370+
g.Expect(crt.GetRequestCount()).To(gmg.Equal(18))
335371
})
336372

337373
t.Run("LazyRESTMapper should return an error if a resource doesn't exist", func(t *testing.T) {
@@ -529,3 +565,35 @@ func TestLazyRestMapperProvider(t *testing.T) {
529565
g.Expect(mapping.GroupVersionKind.Kind).To(gmg.Equal("rider"))
530566
})
531567
}
568+
569+
func beNoMatchError() gomegatypes.GomegaMatcher {
570+
return &errorMatcher{
571+
checkFunc: meta.IsNoMatchError,
572+
message: "NoMatch",
573+
}
574+
}
575+
576+
type errorMatcher struct {
577+
checkFunc func(error) bool
578+
message string
579+
}
580+
581+
func (e *errorMatcher) Match(actual interface{}) (success bool, err error) {
582+
if actual == nil {
583+
return false, nil
584+
}
585+
586+
actualErr, actualOk := actual.(error)
587+
if !actualOk {
588+
return false, fmt.Errorf("expected an error-type. got:\n%s", format.Object(actual, 1))
589+
}
590+
591+
return e.checkFunc(actualErr), nil
592+
}
593+
594+
func (e *errorMatcher) FailureMessage(actual interface{}) (message string) {
595+
return format.Message(actual, fmt.Sprintf("to be %s error", e.message))
596+
}
597+
func (e *errorMatcher) NegatedFailureMessage(actual interface{}) (message string) {
598+
return format.Message(actual, fmt.Sprintf("not to be %s error", e.message))
599+
}

0 commit comments

Comments
 (0)