Skip to content

Commit 5cfe589

Browse files
committed
Update error handling in reconciler
Signed-off-by: Todd Short <[email protected]>
1 parent 2402bb6 commit 5cfe589

File tree

2 files changed

+40
-88
lines changed

2 files changed

+40
-88
lines changed

internal/controllers/clusterextension_controller.go

+34-84
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,12 @@ import (
3838
"helm.sh/helm/v3/pkg/storage/driver"
3939
corev1 "k8s.io/api/core/v1"
4040
"k8s.io/apimachinery/pkg/api/equality"
41-
apierrors "k8s.io/apimachinery/pkg/api/errors"
4241
apimeta "k8s.io/apimachinery/pkg/api/meta"
4342
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4443
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
4544
"k8s.io/apimachinery/pkg/runtime"
4645
"k8s.io/apimachinery/pkg/runtime/schema"
4746
"k8s.io/apimachinery/pkg/types"
48-
utilerrors "k8s.io/apimachinery/pkg/util/errors"
4947
"k8s.io/apimachinery/pkg/util/sets"
5048
apimachyaml "k8s.io/apimachinery/pkg/util/yaml"
5149
ctrl "sigs.k8s.io/controller-runtime"
@@ -80,6 +78,17 @@ import (
8078
"github.com/operator-framework/operator-controller/internal/labels"
8179
)
8280

81+
// ResolutionError type
82+
// If a more specific error type needs to be distinguished,
83+
// add another type here
84+
type ResolutionError struct {
85+
message string
86+
}
87+
88+
func (e ResolutionError) Error() string {
89+
return e.message
90+
}
91+
8392
// ClusterExtensionReconciler reconciles a ClusterExtension object
8493
type ClusterExtensionReconciler struct {
8594
client.Client
@@ -115,40 +124,35 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
115124

116125
var existingExt = &ocv1alpha1.ClusterExtension{}
117126
if err := r.Client.Get(ctx, req.NamespacedName, existingExt); err != nil {
118-
return ctrl.Result{}, utilerrors.NewAggregate([]error{client.IgnoreNotFound(err), nil})
127+
return ctrl.Result{}, client.IgnoreNotFound(err)
119128
}
120129

121-
reconciledExt := existingExt.DeepCopy()
122-
res, reconcileErr := r.reconcile(ctx, reconciledExt)
130+
var updateError error
123131

124-
var updateErrors []error
132+
reconciledExt := existingExt.DeepCopy()
133+
res, err := r.reconcile(ctx, reconciledExt)
134+
updateError = errors.Join(updateError, err)
125135

126136
// Do checks before any Update()s, as Update() may modify the resource structure!
127137
updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status)
128138
updateFinalizers := !equality.Semantic.DeepEqual(existingExt.Finalizers, reconciledExt.Finalizers)
129139
unexpectedFieldsChanged := checkForUnexpectedFieldChange(*existingExt, *reconciledExt)
130140

131141
if updateStatus {
132-
if updateErr := r.Client.Status().Update(ctx, reconciledExt); updateErr != nil {
133-
updateErrors = append(updateErrors, updateErr)
134-
}
142+
err = r.Client.Status().Update(ctx, reconciledExt)
143+
updateError = errors.Join(updateError, err)
135144
}
136145

137146
if unexpectedFieldsChanged {
138147
panic("spec or metadata changed by reconciler")
139148
}
140149

141150
if updateFinalizers {
142-
if updateErr := r.Client.Update(ctx, reconciledExt); updateErr != nil {
143-
updateErrors = append(updateErrors, updateErr)
144-
}
145-
}
146-
147-
if reconcileErr != nil {
148-
updateErrors = append(updateErrors, reconcileErr)
151+
err = r.Client.Update(ctx, reconciledExt)
152+
updateError = errors.Join(updateError, err)
149153
}
150154

151-
return res, utilerrors.NewAggregate(updateErrors)
155+
return res, updateError
152156
}
153157

154158
// ensureAllConditionsWithReason checks that all defined condition types exist in the given ClusterExtension,
@@ -179,29 +183,15 @@ func checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool {
179183
}
180184

181185
func (r *ClusterExtensionReconciler) handleResolutionErrors(ext *ocv1alpha1.ClusterExtension, err error) (ctrl.Result, error) {
182-
var aggErrs utilerrors.Aggregate
183-
if errors.As(err, &aggErrs) {
184-
for _, err := range aggErrs.Errors() {
185-
errorMessage := err.Error()
186-
if strings.Contains(errorMessage, "no package") {
187-
// Handle no package found errors, potentially setting status conditions
188-
setResolvedStatusConditionFailed(&ext.Status.Conditions, errorMessage, ext.Generation)
189-
ensureAllConditionsWithReason(ext, "ResolutionFailed", errorMessage)
190-
} else if strings.Contains(errorMessage, "invalid version range") {
191-
// Handle invalid version range errors, potentially setting status conditions
192-
setResolvedStatusConditionFailed(&ext.Status.Conditions, errorMessage, ext.Generation)
193-
ensureAllConditionsWithReason(ext, "ResolutionFailed", errorMessage)
194-
} else {
195-
// General error handling
196-
setResolvedStatusConditionFailed(&ext.Status.Conditions, errorMessage, ext.Generation)
197-
ensureAllConditionsWithReason(ext, "InstallationStatusUnknown", "")
198-
}
199-
}
186+
errorMessage := err.Error()
187+
if errors.As(err, &ResolutionError{}) {
188+
// Handle resolution errors
189+
setResolvedStatusConditionFailed(&ext.Status.Conditions, errorMessage, ext.Generation)
190+
ensureAllConditionsWithReason(ext, ocv1alpha1.ReasonResolutionFailed, errorMessage)
200191
} else {
201-
// If the error is not an aggregate, handle it as a general error
202-
errorMessage := err.Error()
192+
// General error handling
203193
setResolvedStatusConditionFailed(&ext.Status.Conditions, errorMessage, ext.Generation)
204-
ensureAllConditionsWithReason(ext, "InstallationStatusUnknown", "")
194+
ensureAllConditionsWithReason(ext, ocv1alpha1.ReasonInstallationStatusUnknown, errorMessage)
205195
}
206196
ext.Status.ResolvedBundle = nil
207197
return ctrl.Result{}, err
@@ -337,26 +327,17 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
337327
return nil
338328
}, helmclient.AppendInstallPostRenderer(post))
339329
if err != nil {
340-
if isResourceNotFoundErr(err) {
341-
err = errRequiredResourceNotFound{err}
342-
}
343330
setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonInstallationFailed, err), ext.Generation)
344331
return ctrl.Result{}, err
345332
}
346333
case stateNeedsUpgrade:
347334
rel, err = ac.Upgrade(ext.GetName(), r.ReleaseNamespace, chrt, values, helmclient.AppendUpgradePostRenderer(post))
348335
if err != nil {
349-
if isResourceNotFoundErr(err) {
350-
err = errRequiredResourceNotFound{err}
351-
}
352336
setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonUpgradeFailed, err), ext.Generation)
353337
return ctrl.Result{}, err
354338
}
355339
case stateUnchanged:
356340
if err := ac.Reconcile(rel); err != nil {
357-
if isResourceNotFoundErr(err) {
358-
err = errRequiredResourceNotFound{err}
359-
}
360341
setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonResolutionFailed, err), ext.Generation)
361342
return ctrl.Result{}, err
362343
}
@@ -409,7 +390,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
409390
func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext ocv1alpha1.ClusterExtension) (*catalogmetadata.Bundle, error) {
410391
allBundles, err := r.BundleProvider.Bundles(ctx)
411392
if err != nil {
412-
return nil, utilerrors.NewAggregate([]error{fmt.Errorf("error fetching bundles: %w", err)})
393+
return nil, fmt.Errorf("error fetching bundles: %w", err)
413394
}
414395

415396
packageName := ext.Spec.PackageName
@@ -432,7 +413,7 @@ func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext ocv1alpha1
432413
if versionRange != "" {
433414
vr, err := mmsemver.NewConstraint(versionRange)
434415
if err != nil {
435-
return nil, utilerrors.NewAggregate([]error{fmt.Errorf("invalid version range '%s': %w", versionRange, err)})
416+
return nil, ResolutionError{fmt.Sprintf("invalid version range %q: %v", versionRange, err)}
436417
}
437418
predicates = append(predicates, catalogfilter.InMastermindsSemverRange(vr))
438419
}
@@ -459,13 +440,13 @@ func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext ocv1alpha1
459440
if len(resultSet) == 0 {
460441
switch {
461442
case versionRange != "" && channelName != "":
462-
return nil, fmt.Errorf("%sno package %q matching version %q in channel %q found", upgradeErrorPrefix, packageName, versionRange, channelName)
443+
return nil, ResolutionError{fmt.Sprintf("%sno package %q matching version %q in channel %q found", upgradeErrorPrefix, packageName, versionRange, channelName)}
463444
case versionRange != "":
464-
return nil, fmt.Errorf("%sno package %q matching version %q found", upgradeErrorPrefix, packageName, versionRange)
445+
return nil, ResolutionError{fmt.Sprintf("%sno package %q matching version %q found", upgradeErrorPrefix, packageName, versionRange)}
465446
case channelName != "":
466-
return nil, fmt.Errorf("%sno package %q in channel %q found", upgradeErrorPrefix, packageName, channelName)
447+
return nil, ResolutionError{fmt.Sprintf("%sno package %q in channel %q found", upgradeErrorPrefix, packageName, channelName)}
467448
default:
468-
return nil, fmt.Errorf("%sno package %q found", upgradeErrorPrefix, packageName)
449+
return nil, ResolutionError{fmt.Sprintf("%sno package %q found", upgradeErrorPrefix, packageName)}
469450
}
470451
}
471452

@@ -746,37 +727,6 @@ var GetInstalledbundle = func(ctx context.Context, acg helmclient.ActionClientGe
746727
return resultSet[0], nil
747728
}
748729

749-
type errRequiredResourceNotFound struct {
750-
error
751-
}
752-
753-
func (err errRequiredResourceNotFound) Error() string {
754-
return fmt.Sprintf("required resource not found: %v", err.error)
755-
}
756-
757-
func isResourceNotFoundErr(err error) bool {
758-
var agg utilerrors.Aggregate
759-
if errors.As(err, &agg) {
760-
for _, err := range agg.Errors() {
761-
return isResourceNotFoundErr(err)
762-
}
763-
}
764-
765-
nkme := &apimeta.NoKindMatchError{}
766-
if errors.As(err, &nkme) {
767-
return true
768-
}
769-
if apierrors.IsNotFound(err) {
770-
return true
771-
}
772-
773-
// TODO: improve NoKindMatchError matching
774-
// An error that is bubbled up from the k8s.io/cli-runtime library
775-
// does not wrap meta.NoKindMatchError, so we need to fallback to
776-
// the use of string comparisons for now.
777-
return strings.Contains(err.Error(), "no matches for kind")
778-
}
779-
780730
type postrenderer struct {
781731
labels map[string]string
782732
cascade postrender.PostRenderer

internal/controllers/clusterextension_controller_test.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,11 @@ func TestClusterExtensionVersionNoChannel(t *testing.T) {
287287
require.Equal(t, metav1.ConditionFalse, cond.Status)
288288
require.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason)
289289
require.Equal(t, fmt.Errorf("no package \"%v\" matching version \"%v\" in channel \"%v\" found", pkgName, pkgVer, pkgChan).Error(), cond.Message)
290-
cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled)
291290

291+
cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled)
292292
require.NotNil(t, cond)
293293
require.Equal(t, metav1.ConditionFalse, cond.Status)
294-
require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason)
294+
require.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason)
295295

296296
verifyInvariants(ctx, t, reconciler.Client, clusterExtension)
297297
require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{}))
@@ -335,10 +335,11 @@ func TestClusterExtensionNoChannel(t *testing.T) {
335335
require.Equal(t, metav1.ConditionFalse, cond.Status)
336336
require.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason)
337337
require.Equal(t, fmt.Errorf("no package \"%v\" in channel \"%v\" found", pkgName, pkgChan).Error(), cond.Message)
338+
338339
cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled)
339340
require.NotNil(t, cond)
340341
require.Equal(t, metav1.ConditionFalse, cond.Status)
341-
require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason)
342+
require.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason)
342343

343344
verifyInvariants(ctx, t, reconciler.Client, clusterExtension)
344345
require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{}))
@@ -384,10 +385,11 @@ func TestClusterExtensionNoVersion(t *testing.T) {
384385
require.Equal(t, metav1.ConditionFalse, cond.Status)
385386
require.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason)
386387
require.Equal(t, fmt.Errorf("no package \"%v\" matching version \"%v\" in channel \"%v\" found", pkgName, pkgVer, pkgChan).Error(), cond.Message)
388+
387389
cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled)
388390
require.NotNil(t, cond)
389391
require.Equal(t, metav1.ConditionFalse, cond.Status)
390-
require.Equal(t, ocv1alpha1.ReasonInstallationStatusUnknown, cond.Reason)
392+
require.Equal(t, ocv1alpha1.ReasonResolutionFailed, cond.Reason)
391393

392394
verifyInvariants(ctx, t, reconciler.Client, clusterExtension)
393395
require.NoError(t, cl.DeleteAllOf(ctx, &ocv1alpha1.ClusterExtension{}))

0 commit comments

Comments
 (0)