You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 14e29d7
Browse filesBrowse the repository at this point in the historyBrowse files
fix(component)!: stop FlushStatus from writing stale status back on a conflict (#241)
On a 409 Conflict, FlushStatus wrote the staged owner again with the
resourceVersion of the server. The staged owner came from the read at
the top of the reconcile, so the retry wrote old values over the status
fields of other writers. This defeated optimistic locking.
FlushStatus now makes one Status().Update. On any error, a conflict
included, it writes nothing and returns the error, wrapped with %w so
that apierrors.IsConflict still reports true. The controller requeues,
and the next reconcile reads the current owner. A stale write never
lands, so a status field or condition of another writer is never
rolled back.
BREAKING CHANGE: FlushStatus no longer takes the comps argument. The
signature is FlushStatus(ctx, rec). Remove the third argument at each
call site. A conflict is now returned as a reconcile error instead of
being retried. ReconcileContext.APIReader stays for the resource reads
of BlockOnForeignController and DeleteOnlyIfOwned.
Fixes#238
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|`Client`|`client.Client`| The controller-runtime client. |
272
-
|`Scheme`|`*runtime.Scheme`| The operator scheme. |
273
-
|`EventRecorder`|`events.EventRecorder`| For Kubernetes events. `GetEventRecorder(name)` on the manager, v0.23 and later. |
274
-
|`Metrics`|`component.MetricsRecorder`| Optional. `metrics.NewRecorder(...)` records condition and resource metrics; pass `nil` to skip both. See [Metrics](component.md#metrics). |
275
-
|`APIReader`|`client.Reader`| Optional, recommended. `GetAPIReader()` on the manager; `FlushStatus`reads through it on a 409 so the retry sees the live owner, not the informer cache.|
276
-
|`Owner`|`component.OperatorCRD`| The owner object you fetched. Your CRD satisfies this via `GetStatusConditions`. |
|`Client`|`client.Client`| The controller-runtime client. |
272
+
|`Scheme`|`*runtime.Scheme`| The operator scheme. |
273
+
|`EventRecorder`|`events.EventRecorder`| For Kubernetes events. `GetEventRecorder(name)` on the manager, v0.23 and later. |
274
+
|`Metrics`|`component.MetricsRecorder`| Optional. `metrics.NewRecorder(...)` records condition and resource metrics; pass `nil` to skip both. See [Metrics](component.md#metrics). |
275
+
|`APIReader`|`client.Reader`| Optional, recommended. `GetAPIReader()` on the manager; the component reads live objects through it instead of the informer cache. |
276
+
|`Owner`|`component.OperatorCRD`| The owner object you fetched. Your CRD satisfies this via `GetStatusConditions`. |
277
277
278
278
!!! note
279
279
280
-
`FlushStatus` performs the status write itself: it calls `Client.Status().Update` on the owner (retrying on
281
-
conflict). Do not also call `Status().Update` for the conditions the framework manages, or you will double-write.
282
-
Because the call is deferred, the conditions are flushed even if `Reconcile` returns an error. Pass the components
283
-
you built: on a conflict their condition types stay on the staged owner, while every other condition is refreshed
284
-
from the server.
280
+
`FlushStatus` performs the status write itself: it calls `Client.Status().Update` on the owner once. Do not also
281
+
call `Status().Update` for the conditions the framework manages, or you will double-write. Because the call is
282
+
deferred, the conditions are flushed even if `Reconcile` returns an error. If another writer updated the owner after
283
+
you fetched it, the write fails with a conflict and nothing is written. The error requeues the owner, and the next
284
+
reconcile starts from the current object.
285
285
286
286
```go
287
287
package app
@@ -300,7 +300,7 @@ type Controller struct {
300
300
Scheme *runtime.Scheme
301
301
EventRecorder events.EventRecorder
302
302
Metrics component.MetricsRecorder
303
-
APIReader client.Reader// from mgr.GetAPIReader(); direct reads on a status conflict
303
+
APIReader client.Reader// from mgr.GetAPIReader(); direct reads of live objects
0 commit comments