@@ -16,7 +16,6 @@ import (
1616 "k8s.io/apimachinery/pkg/api/equality"
1717 "k8s.io/apimachinery/pkg/api/meta"
1818 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019 "k8s.io/apimachinery/pkg/runtime/schema"
2120 "k8s.io/apimachinery/pkg/types"
2221 "k8s.io/apimachinery/pkg/util/sets"
@@ -116,7 +115,17 @@ func checkForUnexpectedClusterExtensionRevisionFieldChange(a, b ocv1.ClusterExte
116115func (c * ClusterExtensionRevisionReconciler ) reconcile (ctx context.Context , rev * ocv1.ClusterExtensionRevision ) (ctrl.Result , error ) {
117116 l := log .FromContext (ctx )
118117
119- revision , opts , previous := toBoxcutterRevision (rev )
118+ revision , opts , err := c .toBoxcutterRevision (ctx , rev )
119+ if err != nil {
120+ meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
121+ Type : ocv1 .ClusterExtensionRevisionTypeAvailable ,
122+ Status : metav1 .ConditionFalse ,
123+ Reason : ocv1 .ClusterExtensionRevisionReasonReconcileFailure ,
124+ Message : err .Error (),
125+ ObservedGeneration : rev .Generation ,
126+ })
127+ return ctrl.Result {}, fmt .Errorf ("converting to boxcutter revision: %v" , err )
128+ }
120129
121130 if ! rev .DeletionTimestamp .IsZero () || rev .Spec .LifecycleState == ocv1 .ClusterExtensionRevisionLifecycleStateArchived {
122131 return c .teardown (ctx , rev , revision )
@@ -212,7 +221,11 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
212221
213222 //nolint:nestif
214223 if rres .IsComplete () {
215- // Archive other revisions.
224+ // Archive previous revisions
225+ previous , err := c .ListPreviousRevisions (ctx , rev )
226+ if err != nil {
227+ return ctrl.Result {}, fmt .Errorf ("listing previous revisions: %v" , err )
228+ }
216229 for _ , a := range previous {
217230 patch := []byte (`{"spec":{"lifecycleState":"Archived"}}` )
218231 if err := c .Client .Patch (ctx , a , client .RawPatch (types .MergePatchType , patch )); err != nil {
@@ -428,14 +441,56 @@ func (c *ClusterExtensionRevisionReconciler) removeFinalizer(ctx context.Context
428441 return nil
429442}
430443
431- func toBoxcutterRevision (rev * ocv1.ClusterExtensionRevision ) (* boxcutter.Revision , []boxcutter.RevisionReconcileOption , []client.Object ) {
432- previous := make ([]client.Object , 0 , len (rev .Spec .Previous ))
433- for _ , specPrevious := range rev .Spec .Previous {
434- prev := & unstructured.Unstructured {}
435- prev .SetName (specPrevious .Name )
436- prev .SetUID (specPrevious .UID )
437- prev .SetGroupVersionKind (ocv1 .GroupVersion .WithKind (ocv1 .ClusterExtensionRevisionKind ))
438- previous = append (previous , prev )
444+ // ListPreviousRevisions returns active revisions belonging to the same ClusterExtension.
445+ //
446+ // Scenario: Given a ClusterExtensionRevision, find all related active revisions
447+ // - When the revision has no owner label, return empty list (revision not properly labeled)
448+ // - When looking up by owner label, filter out the current revision itself
449+ // - When a revision is archived or being deleted, exclude it from results
450+ // - Otherwise include active and paused revisions with matching owner
451+ //
452+ // This method uses label-based lookups instead of the revision's Spec.Previous field to find
453+ // related revisions. This approach leverages the cache and is more efficient than tracking
454+ // explicit revision chains.
455+ //
456+ // Used by boxcutter for:
457+ // - Collision detection during rollout
458+ // - Finding revisions to archive after successful deployment
459+ func (c * ClusterExtensionRevisionReconciler ) ListPreviousRevisions (ctx context.Context , rev * ocv1.ClusterExtensionRevision ) ([]client.Object , error ) {
460+ ownerLabel , ok := rev .Labels [ClusterExtensionRevisionOwnerLabel ]
461+ if ! ok {
462+ // No owner label means this revision isn't properly labeled - return empty list
463+ return nil , nil
464+ }
465+
466+ revList := & ocv1.ClusterExtensionRevisionList {}
467+ if err := c .Client .List (ctx , revList , client.MatchingLabels {
468+ ClusterExtensionRevisionOwnerLabel : ownerLabel ,
469+ }); err != nil {
470+ return nil , fmt .Errorf ("listing revisions: %w" , err )
471+ }
472+
473+ previous := make ([]client.Object , 0 , len (revList .Items ))
474+ for i := range revList .Items {
475+ r := & revList .Items [i ]
476+ if r .Name == rev .Name {
477+ continue
478+ }
479+ // Skip archived or deleting revisions
480+ if r .Spec .LifecycleState == ocv1 .ClusterExtensionRevisionLifecycleStateArchived ||
481+ ! r .DeletionTimestamp .IsZero () {
482+ continue
483+ }
484+ previous = append (previous , r )
485+ }
486+
487+ return previous , nil
488+ }
489+
490+ func (c * ClusterExtensionRevisionReconciler ) toBoxcutterRevision (ctx context.Context , rev * ocv1.ClusterExtensionRevision ) (* boxcutter.Revision , []boxcutter.RevisionReconcileOption , error ) {
491+ previous , err := c .ListPreviousRevisions (ctx , rev )
492+ if err != nil {
493+ return nil , nil , fmt .Errorf ("listing previous revisions: %w" , err )
439494 }
440495
441496 opts := []boxcutter.RevisionReconcileOption {
@@ -476,7 +531,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
476531 if rev .Spec .LifecycleState == ocv1 .ClusterExtensionRevisionLifecycleStatePaused {
477532 opts = append (opts , boxcutter.WithPaused {})
478533 }
479- return r , opts , previous
534+ return r , opts , nil
480535}
481536
482537var (
0 commit comments