fix(operator): idle auto-stop couldn't write — patch the spec, and grant the patch

Two stacked blockers behind the frozen auto-stop, both found live after the
first two fixes let the timer finally tick:

- The stop used a whole-object Update while the same reconcile loop writes
  status; that risks clobbering a concurrent status write. Switch to the
  reaper's merge-patch pattern (spec.desiredState only; EmptySince is left for
  markStopped to clear).
- The operator Role never carried minecraftservers:patch, so the call failed
  closed with 403 (visible in the operator log as 'cannot update resource
  "minecraftservers"'). Grant patch and pin it in the RBAC scope test.

With all three layers fixed, the auto-stop path is: timer persists (schema),
wake-up fires (requeue), spec write allowed (RBAC).
This commit is contained in:
Lemon-miaow committed 2026-09-23 04:08:58 +08:00
1 parent 1c89a5eeeb
commit f650bf892a
3 files changed
+18 -5

No files matched your search

+7 -2
View File
@@ -185,9 +185,14 @@ func (r *Reconciler) reconcileRunning(ctx context.Context, server *v1alpha1.Mine
server.Status.EmptySince = &t
} else if r.now().Time.Sub(server.Status.EmptySince.Time).Seconds() >=
float64(server.Spec.Idle.EmptySecondsBeforeStop) {
// Merge patch, not Update: an unrelated reconcile writes status
// concurrently, and shipping the whole object back risks
// clobbering it (the reaper's Stop uses the same pattern for
// the same reason). EmptySince is deliberately left for
// markStopped to clear once the scale-down completes.
patch := client.MergeFrom(server.DeepCopy())
server.Spec.DesiredState = v1alpha1.DesiredStopped
server.Status.EmptySince = nil
if err := r.Update(ctx, server); err != nil {
if err := r.Patch(ctx, server, patch); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil