@@ -12,6 +12,7 @@ import (
1212
1313 appsv1 "k8s.io/api/apps/v1"
1414 corev1 "k8s.io/api/core/v1"
15+ "k8s.io/apimachinery/pkg/api/equality"
1516 "k8s.io/apimachinery/pkg/api/meta"
1617 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1718 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -67,16 +68,44 @@ func (c *ClusterExtensionRevisionReconciler) Reconcile(ctx context.Context, req
6768 l := log .FromContext (ctx ).WithName ("cluster-extension-revision" )
6869 ctx = log .IntoContext (ctx , l )
6970
70- rev := & ocv1.ClusterExtensionRevision {}
71- if err := c .Client .Get (ctx , req .NamespacedName , rev ); err != nil {
71+ existingRev := & ocv1.ClusterExtensionRevision {}
72+ if err := c .Client .Get (ctx , req .NamespacedName , existingRev ); err != nil {
7273 return ctrl.Result {}, client .IgnoreNotFound (err )
7374 }
7475
75- l = l .WithValues ("key" , req .String ())
7676 l .Info ("reconcile starting" )
7777 defer l .Info ("reconcile ending" )
7878
79- return c .reconcile (ctx , rev )
79+ reconciledRev := existingRev .DeepCopy ()
80+ res , reconcileErr := c .reconcile (ctx , reconciledRev )
81+
82+ // Do checks before any Update()s, as Update() may modify the resource structure!
83+ updateStatus := ! equality .Semantic .DeepEqual (existingRev .Status , reconciledRev .Status )
84+
85+ unexpectedFieldsChanged := checkForUnexpectedClusterExtensionRevisionFieldChange (* existingRev , * reconciledRev )
86+ if unexpectedFieldsChanged {
87+ panic ("spec or metadata changed by reconciler" )
88+ }
89+
90+ // NOTE: finalizer updates are performed during c.reconcile as patches, so that reconcile can
91+ // continue performing logic after successfully setting the finalizer. therefore we only need
92+ // to set status here.
93+
94+ if updateStatus {
95+ if err := c .Client .Status ().Update (ctx , reconciledRev ); err != nil {
96+ reconcileErr = errors .Join (reconcileErr , fmt .Errorf ("error updating status: %v" , err ))
97+ }
98+ }
99+
100+ return res , reconcileErr
101+ }
102+
103+ // Compare resources - ignoring status & metadata.finalizers
104+ func checkForUnexpectedClusterExtensionRevisionFieldChange (a , b ocv1.ClusterExtensionRevision ) bool {
105+ a .Status , b .Status = ocv1.ClusterExtensionRevisionStatus {}, ocv1.ClusterExtensionRevisionStatus {}
106+ a .Finalizers , b .Finalizers = []string {}, []string {}
107+ a .ResourceVersion , b .ResourceVersion = "" , ""
108+ return ! equality .Semantic .DeepEqual (a , b )
80109}
81110
82111func (c * ClusterExtensionRevisionReconciler ) reconcile (ctx context.Context , rev * ocv1.ClusterExtensionRevision ) (ctrl.Result , error ) {
@@ -98,11 +127,14 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
98127 Message : err .Error (),
99128 ObservedGeneration : rev .Generation ,
100129 })
101- return ctrl.Result {}, fmt .Errorf ("revision teardown: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
130+ return ctrl.Result {}, fmt .Errorf ("revision teardown: %v " , err )
102131 }
103132
104133 l .Info ("teardown report" , "report" , tres .String ())
105134 if ! tres .IsComplete () {
135+ // TODO: If it is not complete, it seems like it would be good to update
136+ // the status in some way to tell the user that the teardown is still
137+ // in progress.
106138 return ctrl.Result {}, nil
107139 }
108140
@@ -114,9 +146,19 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
114146 Message : err .Error (),
115147 ObservedGeneration : rev .Generation ,
116148 })
117- return ctrl.Result {}, fmt .Errorf ("free cache informers: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
149+ return ctrl.Result {}, fmt .Errorf ("error stopping informers: %v " , err )
118150 }
119- return ctrl.Result {}, c .removeFinalizer (ctx , rev , clusterExtensionRevisionTeardownFinalizer )
151+ if err := c .removeFinalizer (ctx , rev , clusterExtensionRevisionTeardownFinalizer ); err != nil {
152+ meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
153+ Type : "Available" ,
154+ Status : metav1 .ConditionFalse ,
155+ Reason : "ReconcileFailure" ,
156+ Message : err .Error (),
157+ ObservedGeneration : rev .Generation ,
158+ })
159+ return ctrl.Result {}, fmt .Errorf ("error removing teardown finalizer: %v" , err )
160+ }
161+ return ctrl.Result {}, nil
120162 }
121163
122164 //
@@ -130,8 +172,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
130172 Message : err .Error (),
131173 ObservedGeneration : rev .Generation ,
132174 })
133- return ctrl.Result {}, fmt .Errorf ("ensure finalizer: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
175+ return ctrl.Result {}, fmt .Errorf ("error ensuring teardown finalizer: %v " , err )
134176 }
177+
135178 if err := c .establishWatch (ctx , rev , revision ); err != nil {
136179 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
137180 Type : "Available" ,
@@ -140,8 +183,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
140183 Message : err .Error (),
141184 ObservedGeneration : rev .Generation ,
142185 })
143- return ctrl.Result {}, fmt .Errorf ("establish watch: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
186+ return ctrl.Result {}, fmt .Errorf ("establish watch: %v " , err )
144187 }
188+
145189 rres , err := c .RevisionEngine .Reconcile (ctx , * revision , opts ... )
146190 if err != nil {
147191 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
@@ -151,60 +195,69 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
151195 Message : err .Error (),
152196 ObservedGeneration : rev .Generation ,
153197 })
154- return ctrl.Result {}, fmt .Errorf ("revision reconcile: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
198+ return ctrl.Result {}, fmt .Errorf ("revision reconcile: %v " , err )
155199 }
156200 l .Info ("reconcile report" , "report" , rres .String ())
157201
158202 // Retry failing preflight checks with a flat 10s retry.
159203 // TODO: report status, backoff?
160204 if verr := rres .GetValidationError (); verr != nil {
161205 l .Info ("preflight error, retrying after 10s" , "err" , verr .String ())
206+
162207 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
163208 Type : "Available" ,
164209 Status : metav1 .ConditionFalse ,
165210 Reason : "RevisionValidationFailure" ,
166211 Message : fmt .Sprintf ("revision validation error: %s" , verr ),
167212 ObservedGeneration : rev .Generation ,
168213 })
169- return ctrl.Result {RequeueAfter : 10 * time .Second }, c . Client . Status (). Update ( ctx , rev )
214+ return ctrl.Result {RequeueAfter : 10 * time .Second }, nil
170215 }
216+
171217 for i , pres := range rres .GetPhases () {
172218 if verr := pres .GetValidationError (); verr != nil {
173219 l .Info ("preflight error, retrying after 10s" , "err" , verr .String ())
220+
174221 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
175222 Type : "Available" ,
176223 Status : metav1 .ConditionFalse ,
177224 Reason : "PhaseValidationError" ,
178225 Message : fmt .Sprintf ("phase %d validation error: %s" , i , verr ),
179226 ObservedGeneration : rev .Generation ,
180227 })
181- return ctrl.Result {RequeueAfter : 10 * time .Second }, c . Client . Status (). Update ( ctx , rev )
228+ return ctrl.Result {RequeueAfter : 10 * time .Second }, nil
182229 }
230+
183231 var collidingObjs []string
184232 for _ , ores := range pres .GetObjects () {
185233 if ores .Action () == machinery .ActionCollision {
186234 collidingObjs = append (collidingObjs , ores .String ())
187235 }
188236 }
237+
189238 if len (collidingObjs ) > 0 {
190239 l .Info ("object collision error, retrying after 10s" , "collisions" , collidingObjs )
240+
191241 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
192242 Type : "Available" ,
193243 Status : metav1 .ConditionFalse ,
194244 Reason : "ObjectCollisions" ,
195245 Message : fmt .Sprintf ("revision object collisions in phase %d\n %s" , i , strings .Join (collidingObjs , "\n \n " )),
196246 ObservedGeneration : rev .Generation ,
197247 })
198- return ctrl.Result {RequeueAfter : 10 * time .Second }, c . Client . Status (). Update ( ctx , rev )
248+ return ctrl.Result {RequeueAfter : 10 * time .Second }, nil
199249 }
200250 }
201251
202252 //nolint:nestif
203253 if rres .IsComplete () {
204254 // Archive other revisions.
205255 for _ , a := range previous {
206- if err := c .Client .Patch (ctx , a , client .RawPatch (
207- types .MergePatchType , []byte (`{"spec":{"lifecycleState":"Archived"}}` ))); err != nil {
256+ patch := []byte (`{"spec":{"lifecycleState":"Archived"}}` )
257+ if err := c .Client .Patch (ctx , a , client .RawPatch (types .MergePatchType , patch )); err != nil {
258+ // TODO: It feels like an error here needs to propagate to a status _somewhere_.
259+ // Not sure the current CER makes sense? But it also feels off to set the CE
260+ // status from outside the CE reconciler.
208261 return ctrl.Result {}, fmt .Errorf ("archive previous Revision: %w" , err )
209262 }
210263 }
@@ -217,6 +270,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
217270 Message : "Object is available and passes all probes." ,
218271 ObservedGeneration : rev .Generation ,
219272 })
273+
220274 if ! meta .IsStatusConditionTrue (rev .Status .Conditions , "Succeeded" ) {
221275 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
222276 Type : "Succeeded" ,
@@ -278,7 +332,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
278332 meta .RemoveStatusCondition (& rev .Status .Conditions , "Progressing" )
279333 }
280334
281- return ctrl.Result {}, c . Client . Status (). Update ( ctx , rev )
335+ return ctrl.Result {}, nil
282336}
283337
284338type Sourcerer interface {
@@ -370,7 +424,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
370424 for _ , specPhase := range rev .Spec .Phases {
371425 phase := boxcutter.Phase {Name : specPhase .Name }
372426 for _ , specObj := range specPhase .Objects {
373- obj := specObj .Object
427+ obj := specObj .Object . DeepCopy ()
374428
375429 labels := obj .GetLabels ()
376430 if labels == nil {
@@ -379,7 +433,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
379433 labels [ClusterExtensionRevisionOwnerLabel ] = rev .Labels [ClusterExtensionRevisionOwnerLabel ]
380434 obj .SetLabels (labels )
381435
382- phase .Objects = append (phase .Objects , obj )
436+ phase .Objects = append (phase .Objects , * obj )
383437 }
384438 r .Phases = append (r .Phases , phase )
385439 }
0 commit comments