@@ -25,6 +25,7 @@ import (
2525 "sort"
2626 "strings"
2727 "sync"
28+ "time"
2829
2930 mmsemver "github.com/Masterminds/semver/v3"
3031 bsemver "github.com/blang/semver/v4"
@@ -59,6 +60,7 @@ import (
5960 "sigs.k8s.io/controller-runtime/pkg/reconcile"
6061 "sigs.k8s.io/controller-runtime/pkg/source"
6162
63+ "github.com/operator-framework/api/pkg/operators/v1alpha1"
6264 catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
6365 helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
6466 "github.com/operator-framework/operator-registry/alpha/declcfg"
@@ -72,6 +74,7 @@ import (
7274 "github.com/operator-framework/operator-controller/internal/catalogmetadata"
7375 catalogfilter "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter"
7476 catalogsort "github.com/operator-framework/operator-controller/internal/catalogmetadata/sort"
77+ "github.com/operator-framework/operator-controller/internal/conditionsets"
7578 "github.com/operator-framework/operator-controller/internal/handler"
7679 "github.com/operator-framework/operator-controller/internal/labels"
7780 "github.com/operator-framework/operator-controller/internal/packageerrors"
@@ -111,14 +114,13 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
111114
112115 var existingExt = & ocv1alpha1.ClusterExtension {}
113116 if err := r .Client .Get (ctx , req .NamespacedName , existingExt ); err != nil {
114- return ctrl.Result {}, client .IgnoreNotFound (err )
117+ return ctrl.Result {}, utilerrors . NewAggregate ([] error { client .IgnoreNotFound (err ), nil } )
115118 }
116119
117120 reconciledExt := existingExt .DeepCopy ()
118121 res , reconcileErr := r .reconcile (ctx , reconciledExt )
119- if reconcileErr != nil {
120- return ctrl.Result {}, reconcileErr
121- }
122+
123+ var updateErrors []error
122124
123125 // Do checks before any Update()s, as Update() may modify the resource structure!
124126 updateStatus := ! equality .Semantic .DeepEqual (existingExt .Status , reconciledExt .Status )
@@ -127,7 +129,7 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
127129
128130 if updateStatus {
129131 if updateErr := r .Client .Status ().Update (ctx , reconciledExt ); updateErr != nil {
130- return res , utilerrors . NewAggregate ([] error { reconcileErr , updateErr } )
132+ updateErrors = append ( updateErrors , updateErr )
131133 }
132134 }
133135
@@ -137,11 +139,35 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
137139
138140 if updateFinalizers {
139141 if updateErr := r .Client .Update (ctx , reconciledExt ); updateErr != nil {
140- return res , utilerrors . NewAggregate ([] error { reconcileErr , updateErr } )
142+ updateErrors = append ( updateErrors , updateErr )
141143 }
142144 }
143145
144- return res , reconcileErr
146+ if reconcileErr != nil {
147+ updateErrors = append (updateErrors , reconcileErr )
148+ }
149+
150+ return res , utilerrors .NewAggregate (updateErrors )
151+ }
152+
153+ // ensureAllConditionsWithReason checks that all defined condition types exist in the given ClusterExtension,
154+ // and assigns a specified reason and custom message to any missing condition.
155+ func ensureAllConditionsWithReason (ext * ocv1alpha1.ClusterExtension , reason v1alpha1.ConditionReason , message string ) {
156+ for _ , condType := range conditionsets .ConditionTypes {
157+ cond := apimeta .FindStatusCondition (ext .Status .Conditions , condType )
158+ if cond == nil {
159+ // Create a new condition with a valid reason and add it
160+ newCond := metav1.Condition {
161+ Type : condType ,
162+ Status : metav1 .ConditionFalse ,
163+ Reason : string (reason ),
164+ Message : message ,
165+ ObservedGeneration : ext .GetGeneration (),
166+ LastTransitionTime : metav1 .NewTime (time .Now ()),
167+ }
168+ ext .Status .Conditions = append (ext .Status .Conditions , newCond )
169+ }
170+ }
145171}
146172
147173// Compare resources - ignoring status & metadata.finalizers
@@ -151,6 +177,34 @@ func checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool {
151177 return ! equality .Semantic .DeepEqual (a , b )
152178}
153179
180+ func (r * ClusterExtensionReconciler ) handleResolutionErrors (ext * ocv1alpha1.ClusterExtension , err error ) (ctrl.Result , error ) {
181+ var aggErrs utilerrors.Aggregate
182+ if errors .As (err , & aggErrs ) {
183+ for _ , err := range aggErrs .Errors () {
184+ errorMessage := err .Error ()
185+ if strings .Contains (errorMessage , "no package" ) {
186+ // Handle no package found errors, potentially setting status conditions
187+ setResolvedStatusConditionFailed (& ext .Status .Conditions , errorMessage , ext .Generation )
188+ ensureAllConditionsWithReason (ext , "ResolutionFailed" , errorMessage )
189+ } else if strings .Contains (errorMessage , "invalid version range" ) {
190+ // Handle invalid version range errors, potentially setting status conditions
191+ setResolvedStatusConditionFailed (& ext .Status .Conditions , errorMessage , ext .Generation )
192+ ensureAllConditionsWithReason (ext , "ResolutionFailed" , errorMessage )
193+ } else {
194+ // General error handling
195+ setResolvedStatusConditionFailed (& ext .Status .Conditions , errorMessage , ext .Generation )
196+ ensureAllConditionsWithReason (ext , "InstallationStatusUnknown" , "" )
197+ }
198+ }
199+ } else {
200+ // If the error is not an aggregate, handle it as a general error
201+ errorMessage := err .Error ()
202+ setResolvedStatusConditionFailed (& ext .Status .Conditions , errorMessage , ext .Generation )
203+ ensureAllConditionsWithReason (ext , "InstallationStatusUnknown" , "" )
204+ }
205+ return ctrl.Result {}, err
206+ }
207+
154208// Helper function to do the actual reconcile
155209//
156210// Today we always return ctrl.Result{} and an error.
@@ -159,13 +213,12 @@ func checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool {
159213//
160214//nolint:unparam
161215func (r * ClusterExtensionReconciler ) reconcile (ctx context.Context , ext * ocv1alpha1.ClusterExtension ) (ctrl.Result , error ) {
162- // Lookup the bundle that corresponds to the ClusterExtension's desired package.
163- bundle , err := r .resolve (ctx , ext )
216+ l := log .FromContext (ctx ).WithName ("operator-controller" )
217+ // run resolution
218+ bundle , err := r .resolve (ctx , * ext )
164219 if err != nil {
165- ext .Status .ResolvedBundle = nil
166- ext .Status .InstalledBundle = nil
167- setInstalledStatusConditionFailed (& ext .Status .Conditions , fmt .Sprintf ("%s:%v" , "unable to get resolved bundle version" , err ), ext .Generation )
168- return ctrl.Result {}, err
220+ l .V (1 ).Info ("bundle resolve error" , "error" , err )
221+ return r .handleResolutionErrors (ext , err )
169222 }
170223
171224 bundleVersion , err := bundle .Version ()
@@ -330,13 +383,12 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
330383 return ctrl.Result {}, nil
331384}
332385
333- func (r * ClusterExtensionReconciler ) resolve (ctx context.Context , ext * ocv1alpha1.ClusterExtension ) (* catalogmetadata.Bundle , error ) {
386+ func (r * ClusterExtensionReconciler ) resolve (ctx context.Context , ext ocv1alpha1.ClusterExtension ) (* catalogmetadata.Bundle , error ) {
334387 allBundles , err := r .BundleProvider .Bundles (ctx )
335388 if err != nil {
336- return nil , err
389+ return nil , utilerrors . NewAggregate ([] error { fmt . Errorf ( "error fetching bundles: %w" , err )})
337390 }
338391
339- // TODO: change ext spec to contain a source field.
340392 packageName := ext .Spec .PackageName
341393 channelName := ext .Spec .Channel
342394 versionRange := ext .Spec .Version
@@ -352,47 +404,53 @@ func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext *ocv1alpha
352404 if versionRange != "" {
353405 vr , err := mmsemver .NewConstraint (versionRange )
354406 if err != nil {
355- return nil , fmt .Errorf ("invalid version range %q : %w" , versionRange , err )
407+ return nil , utilerrors . NewAggregate ([] error { fmt .Errorf ("invalid version range '%s' : %w" , versionRange , err )} )
356408 }
357409 predicates = append (predicates , catalogfilter .InMastermindsSemverRange (vr ))
358410 }
359411
360412 var installedVersion string
361- // Do not include bundle versions older than currently installed unless UpgradeConstraintPolicy = 'Ignore'
413+ var upgradeErrorPrefix string
362414 if ext .Spec .UpgradeConstraintPolicy != ocv1alpha1 .UpgradeConstraintPolicyIgnore {
363- installedVersionSemver , err := r .getInstalledVersion (ctx , * ext )
364- if err != nil && ! apierrors . IsNotFound ( err ) {
365- return nil , err
415+ installedBundle , err := r .getInstalledVersion (ctx , ext )
416+ if err != nil {
417+ return nil , utilerrors . NewAggregate ([] error { fmt . Errorf ( "error fetching installed version: %w" , err )})
366418 }
367- if installedVersionSemver != nil {
368- installedVersion = installedVersionSemver .String ()
369-
370- // Based on installed version create a caret range comparison constraint
371- // to allow only minor and patch version as successors.
419+ if installedBundle != nil {
420+ installedVersion = installedBundle .String ()
421+ upgradeErrorPrefix = fmt .Sprintf ("error upgrading from currently installed version %q: " , installedVersion )
372422 wantedVersionRangeConstraint , err := mmsemver .NewConstraint (fmt .Sprintf ("^%s" , installedVersion ))
373423 if err != nil {
374- return nil , err
424+ return nil , utilerrors . NewAggregate ([] error { fmt . Errorf ( "%serror creating version constraint: %w" , upgradeErrorPrefix , err )})
375425 }
376426 predicates = append (predicates , catalogfilter .InMastermindsSemverRange (wantedVersionRangeConstraint ))
377427 }
378428 }
379429
380430 resultSet := catalogfilter .Filter (allBundles , catalogfilter .And (predicates ... ))
381-
382431 if len (resultSet ) == 0 {
383- return nil , packageerrors .GenerateFullError (packageName , versionRange , channelName , installedVersion )
432+ switch {
433+ case versionRange != "" && channelName != "" :
434+ return nil , packageerrors .GenerateVersionChannelError (packageName , versionRange , channelName )
435+ case versionRange != "" :
436+ return nil , packageerrors .GenerateVersionError (packageName , versionRange )
437+ case channelName != "" :
438+ return nil , packageerrors .GenerateChannelError (packageName , channelName )
439+ default :
440+ return nil , packageerrors .GenerateError (packageName )
441+ }
384442 }
385-
386443 sort .SliceStable (resultSet , func (i , j int ) bool {
387444 return catalogsort .ByVersion (resultSet [i ], resultSet [j ])
388445 })
389446 sort .SliceStable (resultSet , func (i , j int ) bool {
390447 return catalogsort .ByDeprecated (resultSet [i ], resultSet [j ])
391448 })
449+
392450 return resultSet [0 ], nil
393451}
394452
395- // setDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension
453+ // SetDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension
396454// based on the provided bundle
397455func SetDeprecationStatus (ext * ocv1alpha1.ClusterExtension , bundle * catalogmetadata.Bundle ) {
398456 // reset conditions to false
0 commit comments