@@ -10,8 +10,10 @@ import (
1010 "strings"
1111 "time"
1212
13+ "github.com/google/go-cmp/cmp"
1314 appsv1 "k8s.io/api/apps/v1"
1415 corev1 "k8s.io/api/core/v1"
16+ "k8s.io/apimachinery/pkg/api/equality"
1517 "k8s.io/apimachinery/pkg/api/meta"
1618 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1719 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -67,16 +69,52 @@ func (c *ClusterExtensionRevisionReconciler) Reconcile(ctx context.Context, req
6769 l := log .FromContext (ctx ).WithName ("cluster-extension-revision" )
6870 ctx = log .IntoContext (ctx , l )
6971
70- rev := & ocv1.ClusterExtensionRevision {}
71- if err := c .Client .Get (ctx , req .NamespacedName , rev ); err != nil {
72+ existingRev := & ocv1.ClusterExtensionRevision {}
73+ if err := c .Client .Get (ctx , req .NamespacedName , existingRev ); err != nil {
7274 return ctrl.Result {}, client .IgnoreNotFound (err )
7375 }
7476
75- l = l .WithValues ("key" , req .String ())
7677 l .Info ("reconcile starting" )
7778 defer l .Info ("reconcile ending" )
7879
79- return c .reconcile (ctx , rev )
80+ reconciledRev := existingRev .DeepCopy ()
81+ res , reconcileErr := c .reconcile (ctx , reconciledRev )
82+
83+ // Do checks before any Update()s, as Update() may modify the resource structure!
84+ updateStatus := ! equality .Semantic .DeepEqual (existingRev .Status , reconciledRev .Status )
85+
86+ unexpectedFieldsChanged := checkForUnexpectedClusterExtensionRevisionFieldChange (* existingRev , * reconciledRev )
87+ if unexpectedFieldsChanged {
88+ panic ("spec or metadata changed by reconciler" )
89+ }
90+
91+ // NOTE: finalizer updates are performed during c.reconcile as patches, so that reconcile can
92+ // continue performing logic after successfully setting the finalizer. therefore we only need
93+ // to set status here.
94+
95+ if updateStatus {
96+ if err := c .Client .Status ().Update (ctx , reconciledRev ); err != nil {
97+ reconcileErr = errors .Join (reconcileErr , fmt .Errorf ("error updating status: %v" , err ))
98+ }
99+ }
100+
101+ return res , reconcileErr
102+ }
103+
104+ // Compare resources - ignoring status & metadata.finalizers
105+ func checkForUnexpectedClusterExtensionRevisionFieldChange (a , b ocv1.ClusterExtensionRevision ) bool {
106+ a .Status , b .Status = ocv1.ClusterExtensionRevisionStatus {}, ocv1.ClusterExtensionRevisionStatus {}
107+
108+ // when finalizers are updated during reconcile, we expect finalizers, managedFields, and resourceVersion
109+ // to be updated, so we ignore changes in these fields.
110+ a .Finalizers , b .Finalizers = []string {}, []string {}
111+ a .ManagedFields , b .ManagedFields = nil , nil
112+ a .ResourceVersion , b .ResourceVersion = "" , ""
113+ if equality .Semantic .DeepEqual (a , b ) {
114+ return false
115+ }
116+ fmt .Println (cmp .Diff (a , b ))
117+ return true
80118}
81119
82120func (c * ClusterExtensionRevisionReconciler ) reconcile (ctx context.Context , rev * ocv1.ClusterExtensionRevision ) (ctrl.Result , error ) {
@@ -98,11 +136,14 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
98136 Message : err .Error (),
99137 ObservedGeneration : rev .Generation ,
100138 })
101- return ctrl.Result {}, fmt .Errorf ("revision teardown: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
139+ return ctrl.Result {}, fmt .Errorf ("revision teardown: %v " , err )
102140 }
103141
104142 l .Info ("teardown report" , "report" , tres .String ())
105143 if ! tres .IsComplete () {
144+ // TODO: If it is not complete, it seems like it would be good to update
145+ // the status in some way to tell the user that the teardown is still
146+ // in progress.
106147 return ctrl.Result {}, nil
107148 }
108149
@@ -114,9 +155,19 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
114155 Message : err .Error (),
115156 ObservedGeneration : rev .Generation ,
116157 })
117- return ctrl.Result {}, fmt .Errorf ("free cache informers: %w" , errors .Join (err , c .Client .Status ().Update (ctx , rev )))
158+ return ctrl.Result {}, fmt .Errorf ("error stopping informers: %v" , err )
159+ }
160+ if err := c .removeFinalizer (ctx , rev , clusterExtensionRevisionTeardownFinalizer ); err != nil {
161+ meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
162+ Type : "Available" ,
163+ Status : metav1 .ConditionFalse ,
164+ Reason : "ReconcileFailure" ,
165+ Message : err .Error (),
166+ ObservedGeneration : rev .Generation ,
167+ })
168+ return ctrl.Result {}, fmt .Errorf ("error removing teardown finalizer: %v" , err )
118169 }
119- return ctrl.Result {}, c . removeFinalizer ( ctx , rev , clusterExtensionRevisionTeardownFinalizer )
170+ return ctrl.Result {}, nil
120171 }
121172
122173 //
@@ -130,8 +181,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
130181 Message : err .Error (),
131182 ObservedGeneration : rev .Generation ,
132183 })
133- return ctrl.Result {}, fmt .Errorf ("ensure finalizer: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
184+ return ctrl.Result {}, fmt .Errorf ("error ensuring teardown finalizer: %v " , err )
134185 }
186+
135187 if err := c .establishWatch (ctx , rev , revision ); err != nil {
136188 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
137189 Type : "Available" ,
@@ -140,8 +192,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
140192 Message : err .Error (),
141193 ObservedGeneration : rev .Generation ,
142194 })
143- return ctrl.Result {}, fmt .Errorf ("establish watch: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
195+ return ctrl.Result {}, fmt .Errorf ("establish watch: %v " , err )
144196 }
197+
145198 rres , err := c .RevisionEngine .Reconcile (ctx , * revision , opts ... )
146199 if err != nil {
147200 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
@@ -151,60 +204,69 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
151204 Message : err .Error (),
152205 ObservedGeneration : rev .Generation ,
153206 })
154- return ctrl.Result {}, fmt .Errorf ("revision reconcile: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
207+ return ctrl.Result {}, fmt .Errorf ("revision reconcile: %v " , err )
155208 }
156209 l .Info ("reconcile report" , "report" , rres .String ())
157210
158211 // Retry failing preflight checks with a flat 10s retry.
159212 // TODO: report status, backoff?
160213 if verr := rres .GetValidationError (); verr != nil {
161214 l .Info ("preflight error, retrying after 10s" , "err" , verr .String ())
215+
162216 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
163217 Type : "Available" ,
164218 Status : metav1 .ConditionFalse ,
165219 Reason : "RevisionValidationFailure" ,
166220 Message : fmt .Sprintf ("revision validation error: %s" , verr ),
167221 ObservedGeneration : rev .Generation ,
168222 })
169- return ctrl.Result {RequeueAfter : 10 * time .Second }, c . Client . Status (). Update ( ctx , rev )
223+ return ctrl.Result {RequeueAfter : 10 * time .Second }, nil
170224 }
225+
171226 for i , pres := range rres .GetPhases () {
172227 if verr := pres .GetValidationError (); verr != nil {
173228 l .Info ("preflight error, retrying after 10s" , "err" , verr .String ())
229+
174230 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
175231 Type : "Available" ,
176232 Status : metav1 .ConditionFalse ,
177233 Reason : "PhaseValidationError" ,
178234 Message : fmt .Sprintf ("phase %d validation error: %s" , i , verr ),
179235 ObservedGeneration : rev .Generation ,
180236 })
181- return ctrl.Result {RequeueAfter : 10 * time .Second }, c . Client . Status (). Update ( ctx , rev )
237+ return ctrl.Result {RequeueAfter : 10 * time .Second }, nil
182238 }
239+
183240 var collidingObjs []string
184241 for _ , ores := range pres .GetObjects () {
185242 if ores .Action () == machinery .ActionCollision {
186243 collidingObjs = append (collidingObjs , ores .String ())
187244 }
188245 }
246+
189247 if len (collidingObjs ) > 0 {
190248 l .Info ("object collision error, retrying after 10s" , "collisions" , collidingObjs )
249+
191250 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
192251 Type : "Available" ,
193252 Status : metav1 .ConditionFalse ,
194253 Reason : "ObjectCollisions" ,
195254 Message : fmt .Sprintf ("revision object collisions in phase %d\n %s" , i , strings .Join (collidingObjs , "\n \n " )),
196255 ObservedGeneration : rev .Generation ,
197256 })
198- return ctrl.Result {RequeueAfter : 10 * time .Second }, c . Client . Status (). Update ( ctx , rev )
257+ return ctrl.Result {RequeueAfter : 10 * time .Second }, nil
199258 }
200259 }
201260
202261 //nolint:nestif
203262 if rres .IsComplete () {
204263 // Archive other revisions.
205264 for _ , a := range previous {
206- if err := c .Client .Patch (ctx , a , client .RawPatch (
207- types .MergePatchType , []byte (`{"spec":{"lifecycleState":"Archived"}}` ))); err != nil {
265+ patch := []byte (`{"spec":{"lifecycleState":"Archived"}}` )
266+ if err := c .Client .Patch (ctx , a , client .RawPatch (types .MergePatchType , patch )); err != nil {
267+ // TODO: It feels like an error here needs to propagate to a status _somewhere_.
268+ // Not sure the current CER makes sense? But it also feels off to set the CE
269+ // status from outside the CE reconciler.
208270 return ctrl.Result {}, fmt .Errorf ("archive previous Revision: %w" , err )
209271 }
210272 }
@@ -217,6 +279,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
217279 Message : "Object is available and passes all probes." ,
218280 ObservedGeneration : rev .Generation ,
219281 })
282+
220283 if ! meta .IsStatusConditionTrue (rev .Status .Conditions , "Succeeded" ) {
221284 meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
222285 Type : "Succeeded" ,
@@ -278,7 +341,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
278341 meta .RemoveStatusCondition (& rev .Status .Conditions , "Progressing" )
279342 }
280343
281- return ctrl.Result {}, c . Client . Status (). Update ( ctx , rev )
344+ return ctrl.Result {}, nil
282345}
283346
284347type Sourcerer interface {
@@ -370,7 +433,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
370433 for _ , specPhase := range rev .Spec .Phases {
371434 phase := boxcutter.Phase {Name : specPhase .Name }
372435 for _ , specObj := range specPhase .Objects {
373- obj := specObj .Object
436+ obj := specObj .Object . DeepCopy ()
374437
375438 labels := obj .GetLabels ()
376439 if labels == nil {
@@ -379,7 +442,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
379442 labels [ClusterExtensionRevisionOwnerLabel ] = rev .Labels [ClusterExtensionRevisionOwnerLabel ]
380443 obj .SetLabels (labels )
381444
382- phase .Objects = append (phase .Objects , obj )
445+ phase .Objects = append (phase .Objects , * obj )
383446 }
384447 r .Phases = append (r .Phases , phase )
385448 }
0 commit comments