@@ -12,6 +12,7 @@ import (
12
12
13
13
appsv1 "k8s.io/api/apps/v1"
14
14
corev1 "k8s.io/api/core/v1"
15
+ "k8s.io/apimachinery/pkg/api/equality"
15
16
"k8s.io/apimachinery/pkg/api/meta"
16
17
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17
18
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -67,16 +68,48 @@ func (c *ClusterExtensionRevisionReconciler) Reconcile(ctx context.Context, req
67
68
l := log .FromContext (ctx ).WithName ("cluster-extension-revision" )
68
69
ctx = log .IntoContext (ctx , l )
69
70
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 {
72
73
return ctrl.Result {}, client .IgnoreNotFound (err )
73
74
}
74
75
75
- l = l .WithValues ("key" , req .String ())
76
76
l .Info ("reconcile starting" )
77
77
defer l .Info ("reconcile ending" )
78
78
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
+
107
+ // when finalizers are updated during reconcile, we expect finalizers, managedFields, and resourceVersion
108
+ // to be updated, so we ignore changes in these fields.
109
+ a .Finalizers , b .Finalizers = []string {}, []string {}
110
+ a .ManagedFields , b .ManagedFields = nil , nil
111
+ a .ResourceVersion , b .ResourceVersion = "" , ""
112
+ return ! equality .Semantic .DeepEqual (a .Spec , b .Spec )
80
113
}
81
114
82
115
func (c * ClusterExtensionRevisionReconciler ) reconcile (ctx context.Context , rev * ocv1.ClusterExtensionRevision ) (ctrl.Result , error ) {
@@ -98,11 +131,14 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
98
131
Message : err .Error (),
99
132
ObservedGeneration : rev .Generation ,
100
133
})
101
- return ctrl.Result {}, fmt .Errorf ("revision teardown: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
134
+ return ctrl.Result {}, fmt .Errorf ("revision teardown: %v " , err )
102
135
}
103
136
104
137
l .Info ("teardown report" , "report" , tres .String ())
105
138
if ! tres .IsComplete () {
139
+ // TODO: If it is not complete, it seems like it would be good to update
140
+ // the status in some way to tell the user that the teardown is still
141
+ // in progress.
106
142
return ctrl.Result {}, nil
107
143
}
108
144
@@ -114,9 +150,19 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
114
150
Message : err .Error (),
115
151
ObservedGeneration : rev .Generation ,
116
152
})
117
- return ctrl.Result {}, fmt .Errorf ("free cache informers: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
153
+ return ctrl.Result {}, fmt .Errorf ("error stopping informers: %v " , err )
118
154
}
119
- return ctrl.Result {}, c .removeFinalizer (ctx , rev , clusterExtensionRevisionTeardownFinalizer )
155
+ if err := c .removeFinalizer (ctx , rev , clusterExtensionRevisionTeardownFinalizer ); err != nil {
156
+ meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
157
+ Type : "Available" ,
158
+ Status : metav1 .ConditionFalse ,
159
+ Reason : "ReconcileFailure" ,
160
+ Message : err .Error (),
161
+ ObservedGeneration : rev .Generation ,
162
+ })
163
+ return ctrl.Result {}, fmt .Errorf ("error removing teardown finalizer: %v" , err )
164
+ }
165
+ return ctrl.Result {}, nil
120
166
}
121
167
122
168
//
@@ -130,8 +176,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
130
176
Message : err .Error (),
131
177
ObservedGeneration : rev .Generation ,
132
178
})
133
- return ctrl.Result {}, fmt .Errorf ("ensure finalizer: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
179
+ return ctrl.Result {}, fmt .Errorf ("error ensuring teardown finalizer: %v " , err )
134
180
}
181
+
135
182
if err := c .establishWatch (ctx , rev , revision ); err != nil {
136
183
meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
137
184
Type : ocv1 .ClusterExtensionRevisionTypeAvailable ,
@@ -140,8 +187,9 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
140
187
Message : err .Error (),
141
188
ObservedGeneration : rev .Generation ,
142
189
})
143
- return ctrl.Result {}, fmt .Errorf ("establish watch: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
190
+ return ctrl.Result {}, fmt .Errorf ("establish watch: %v " , err )
144
191
}
192
+
145
193
rres , err := c .RevisionEngine .Reconcile (ctx , * revision , opts ... )
146
194
if err != nil {
147
195
meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
@@ -151,60 +199,69 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
151
199
Message : err .Error (),
152
200
ObservedGeneration : rev .Generation ,
153
201
})
154
- return ctrl.Result {}, fmt .Errorf ("revision reconcile: %w " , errors . Join ( err , c . Client . Status (). Update ( ctx , rev )) )
202
+ return ctrl.Result {}, fmt .Errorf ("revision reconcile: %v " , err )
155
203
}
156
204
l .Info ("reconcile report" , "report" , rres .String ())
157
205
158
206
// Retry failing preflight checks with a flat 10s retry.
159
207
// TODO: report status, backoff?
160
208
if verr := rres .GetValidationError (); verr != nil {
161
209
l .Info ("preflight error, retrying after 10s" , "err" , verr .String ())
210
+
162
211
meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
163
212
Type : ocv1 .ClusterExtensionRevisionTypeAvailable ,
164
213
Status : metav1 .ConditionFalse ,
165
214
Reason : ocv1 .ClusterExtensionRevisionReasonRevisionValidationFailure ,
166
215
Message : fmt .Sprintf ("revision validation error: %s" , verr ),
167
216
ObservedGeneration : rev .Generation ,
168
217
})
169
- return ctrl.Result {RequeueAfter : 10 * time .Second }, c . Client . Status (). Update ( ctx , rev )
218
+ return ctrl.Result {RequeueAfter : 10 * time .Second }, nil
170
219
}
220
+
171
221
for i , pres := range rres .GetPhases () {
172
222
if verr := pres .GetValidationError (); verr != nil {
173
223
l .Info ("preflight error, retrying after 10s" , "err" , verr .String ())
224
+
174
225
meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
175
226
Type : ocv1 .ClusterExtensionRevisionTypeAvailable ,
176
227
Status : metav1 .ConditionFalse ,
177
228
Reason : ocv1 .ClusterExtensionRevisionReasonPhaseValidationError ,
178
229
Message : fmt .Sprintf ("phase %d validation error: %s" , i , verr ),
179
230
ObservedGeneration : rev .Generation ,
180
231
})
181
- return ctrl.Result {RequeueAfter : 10 * time .Second }, c . Client . Status (). Update ( ctx , rev )
232
+ return ctrl.Result {RequeueAfter : 10 * time .Second }, nil
182
233
}
234
+
183
235
var collidingObjs []string
184
236
for _ , ores := range pres .GetObjects () {
185
237
if ores .Action () == machinery .ActionCollision {
186
238
collidingObjs = append (collidingObjs , ores .String ())
187
239
}
188
240
}
241
+
189
242
if len (collidingObjs ) > 0 {
190
243
l .Info ("object collision error, retrying after 10s" , "collisions" , collidingObjs )
244
+
191
245
meta .SetStatusCondition (& rev .Status .Conditions , metav1.Condition {
192
246
Type : ocv1 .ClusterExtensionRevisionTypeAvailable ,
193
247
Status : metav1 .ConditionFalse ,
194
248
Reason : ocv1 .ClusterExtensionRevisionReasonObjectCollisions ,
195
249
Message : fmt .Sprintf ("revision object collisions in phase %d\n %s" , i , strings .Join (collidingObjs , "\n \n " )),
196
250
ObservedGeneration : rev .Generation ,
197
251
})
198
- return ctrl.Result {RequeueAfter : 10 * time .Second }, c . Client . Status (). Update ( ctx , rev )
252
+ return ctrl.Result {RequeueAfter : 10 * time .Second }, nil
199
253
}
200
254
}
201
255
202
256
//nolint:nestif
203
257
if rres .IsComplete () {
204
258
// Archive other revisions.
205
259
for _ , a := range previous {
206
- if err := c .Client .Patch (ctx , a , client .RawPatch (
207
- types .MergePatchType , []byte (`{"spec":{"lifecycleState":"Archived"}}` ))); err != nil {
260
+ patch := []byte (`{"spec":{"lifecycleState":"Archived"}}` )
261
+ if err := c .Client .Patch (ctx , a , client .RawPatch (types .MergePatchType , patch )); err != nil {
262
+ // TODO: It feels like an error here needs to propagate to a status _somewhere_.
263
+ // Not sure the current CER makes sense? But it also feels off to set the CE
264
+ // status from outside the CE reconciler.
208
265
return ctrl.Result {}, fmt .Errorf ("archive previous Revision: %w" , err )
209
266
}
210
267
}
@@ -278,7 +335,7 @@ func (c *ClusterExtensionRevisionReconciler) reconcile(ctx context.Context, rev
278
335
meta .RemoveStatusCondition (& rev .Status .Conditions , ocv1 .TypeProgressing )
279
336
}
280
337
281
- return ctrl.Result {}, c . Client . Status (). Update ( ctx , rev )
338
+ return ctrl.Result {}, nil
282
339
}
283
340
284
341
type Sourcerer interface {
@@ -408,7 +465,7 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
408
465
for _ , specPhase := range rev .Spec .Phases {
409
466
phase := boxcutter.Phase {Name : specPhase .Name }
410
467
for _ , specObj := range specPhase .Objects {
411
- obj := specObj .Object
468
+ obj := specObj .Object . DeepCopy ()
412
469
413
470
labels := obj .GetLabels ()
414
471
if labels == nil {
@@ -420,10 +477,10 @@ func toBoxcutterRevision(rev *ocv1.ClusterExtensionRevision) (*boxcutter.Revisio
420
477
switch specObj .CollisionProtection {
421
478
case ocv1 .CollisionProtectionIfNoController , ocv1 .CollisionProtectionNone :
422
479
opts = append (opts , boxcutter .WithObjectReconcileOptions (
423
- & obj , boxcutter .WithCollisionProtection (specObj .CollisionProtection )))
480
+ obj , boxcutter .WithCollisionProtection (specObj .CollisionProtection )))
424
481
}
425
482
426
- phase .Objects = append (phase .Objects , obj )
483
+ phase .Objects = append (phase .Objects , * obj )
427
484
}
428
485
r .Phases = append (r .Phases , phase )
429
486
}
0 commit comments