Skip to content

Commit ea7ec88

Browse files
Refactor ClusterExtensionReconciler to improve conflict resolution and data consistency
The error `the object has been modified; please apply your changes to the latest version and try again` occurs when we attempt to update a resource that has changed since it was initially fetched. These changes ensure that we fetch the current state of the resource just before issuing updates, rather than using the version obtained at the start of reconciliation. Additionally: - Deep copy the ClusterExtension object before reconciliation to preserve the original state. - Re-fetch the latest resource version before updating status and finalizers to handle conflicts from concurrent updates. Closes: operator-framework#1414
1 parent aaa0e00 commit ea7ec88

File tree

1 file changed

+11
-12
lines changed

1 file changed

+11
-12
lines changed

internal/controllers/clusterextension_controller.go

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,29 +113,28 @@ func (r *ClusterExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Req
113113
reconciledExt := existingExt.DeepCopy()
114114
res, reconcileErr := r.reconcile(ctx, reconciledExt)
115115

116-
// Do checks before any Update()s, as Update() may modify the resource structure!
117-
updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status)
118-
updateFinalizers := !equality.Semantic.DeepEqual(existingExt.Finalizers, reconciledExt.Finalizers)
119-
120116
// If any unexpected fields have changed, panic before updating the resource
121117
unexpectedFieldsChanged := checkForUnexpectedFieldChange(*existingExt, *reconciledExt)
122118
if unexpectedFieldsChanged {
123119
panic("spec or metadata changed by reconciler")
124120
}
125121

126-
// Save the finalizers off to the side. If we update the status, the reconciledExt will be updated
127-
// to contain the new state of the ClusterExtension, which contains the status update, but (critically)
128-
// does not contain the finalizers. After the status update, we need to re-add the finalizers to the
129-
// reconciledExt before updating the object.
130-
finalizers := reconciledExt.Finalizers
131-
if updateStatus {
122+
// Re-fetch the object to get the latest resource version
123+
// This is necessary because the object may have been updated by another controller
124+
// between the time we fetched it and the time we finished reconciling it
125+
if err := r.Client.Get(ctx, req.NamespacedName, existingExt); err != nil {
126+
return ctrl.Result{}, client.IgnoreNotFound(err)
127+
}
128+
if !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status) {
132129
if err := r.Client.Status().Update(ctx, reconciledExt); err != nil {
133130
reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating status: %v", err))
134131
}
135132
}
136-
reconciledExt.Finalizers = finalizers
137133

138-
if updateFinalizers {
134+
if err := r.Client.Get(ctx, req.NamespacedName, existingExt); err != nil {
135+
return ctrl.Result{}, client.IgnoreNotFound(err)
136+
}
137+
if !equality.Semantic.DeepEqual(existingExt.Finalizers, reconciledExt.Finalizers) {
139138
if err := r.Client.Update(ctx, reconciledExt); err != nil {
140139
reconcileErr = errors.Join(reconcileErr, fmt.Errorf("error updating finalizers: %v", err))
141140
}

0 commit comments

Comments
 (0)