diff --git a/cmd/cluster-node-tuning-operator/main.go b/cmd/cluster-node-tuning-operator/main.go index 5f8494a21d..a96fc350a2 100644 --- a/cmd/cluster-node-tuning-operator/main.go +++ b/cmd/cluster-node-tuning-operator/main.go @@ -225,13 +225,13 @@ func removePerformanceOLMOperator(cfg *rest.Config) error { } performanceOperatorCSVs, err := paocontroller.ListPerformanceOperatorCSVs(k8sclient, options, paginationLimit, performanceOperatorDeploymentName) - if err != nil { + if err != nil && !util.IsNoMatchError(err) { return err } subscriptions := &olmv1alpha1.SubscriptionList{} if err := k8sclient.List(context.TODO(), subscriptions); err != nil { - if !errors.IsNotFound(err) { + if !errors.IsNotFound(err) && !util.IsNoMatchError(err) { return err } } diff --git a/pkg/util/errors.go b/pkg/util/errors.go new file mode 100644 index 0000000000..7dd9773ec0 --- /dev/null +++ b/pkg/util/errors.go @@ -0,0 +1,24 @@ +package util + +import ( + "errors" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/discovery" +) + +// IsNoMatchError checks if error is for a non existant resource, there is a meaningful difference between a +// resource type not existing on the cluster as a whole versus an individual resource not being found. +// +// Example: +// OLM can be an optional operator, when the OLM resources do not exist, the returned +// error is a discovery error of meta.NoResourceMatchError. +// +// A bug is present in controller-runtime@v0.16.1 and older where the returned error type is a DiscoveryFailedError +// this was fixed in https://github.com/kubernetes-sigs/controller-runtime/pull/2472 and versions of controller-runtime@v0.16.2 +// going forward will return the meta.NoResourceMatchError error. Here we check if either one is true. +func IsNoMatchError(err error) bool { + // We use errors.As instead of discovery.IsGroupDiscoveryFailedError because it Unwraps errors. + _err := &discovery.ErrGroupDiscoveryFailed{} + return meta.IsNoMatchError(err) || errors.As(err, &_err) +} diff --git a/pkg/util/errors_test.go b/pkg/util/errors_test.go new file mode 100644 index 0000000000..5619b4ea56 --- /dev/null +++ b/pkg/util/errors_test.go @@ -0,0 +1,81 @@ +package util + +import ( + "errors" + "fmt" + "testing" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" +) + +func mockAPIStatusError(reason metav1.StatusReason, code int) *apierrors.StatusError { + return &apierrors.StatusError{ErrStatus: metav1.Status{ + Reason: reason, + Code: int32(code), + }} +} + +func TestIsNoMatchError(t *testing.T) { + defaultErr := discovery.ErrGroupDiscoveryFailed{ + Groups: map[schema.GroupVersion]error{ + { + Group: "apps", + Version: "v1", + }: fmt.Errorf("resource does not exist"), + }, + } + + testCases := []struct { + desc string + err error + expectedMatch bool + }{ + { + desc: "should match discovery.ErrGroupDiscoveryFailed", + err: &defaultErr, + expectedMatch: true, + }, + { + desc: "should match meta.NoResourceMatchError", + err: &meta.NoResourceMatchError{}, + expectedMatch: true, + }, + { + desc: "should unwrap error tree and match discovery.ErrGroupDiscoveryFailed", + err: fmt.Errorf("wrapped error %w", fmt.Errorf("inner %w", &defaultErr)), + expectedMatch: true, + }, + { + desc: "should unwrap error tree and match meta.NoResourceMatchError", + err: fmt.Errorf("wrapped error %w", fmt.Errorf("inner %w", &meta.NoResourceMatchError{})), + expectedMatch: true, + }, + { + desc: "should not match regular error", + err: errors.New("some other error"), + expectedMatch: false, + }, + { + desc: "should not match on api error: NotFound", + err: mockAPIStatusError(metav1.StatusReasonNotFound, 404), + expectedMatch: false, + }, + { + desc: "should not match on api error: Gone", + err: mockAPIStatusError(metav1.StatusReasonGone, 410), + expectedMatch: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + if IsNoMatchError(tc.err) != tc.expectedMatch { + t.Errorf("error did not match expected: %T", tc.err) + } + }) + } +}