fix(patching): final review fixes
Chart Release / chart (push) Successful in 19s
Server Deploy / deploy (push) Successful in 6m12s

- no dispatch in the last 15 minutes of a window; no-result timeout from dispatch time
- per-server output moves to patch_run_outputs (16MB document limit)
- reboot proven by a changed boot time; RebootTimeout 45m, ResultGrace 20m
- window update and delete are server-scoped against the policies using them
- scheduler puts the claim back on an error after it, so the next tick retries
- cancelled runs with failures alert; MCP apply_updates audits per server
- apply-updates 503 body documented; openapi regenerated
- web: cleared numeric fields no longer save as 0; Run now asks for confirmation
This commit is contained in:
2026-09-15 13:49:28 +00:00
parent 3ecea7c39f
commit 3f2d20868e
22 changed files with 436 additions and 71 deletions
+16 -3
View File
@@ -96,18 +96,18 @@ func process(ctx context.Context, deps Deps, p models.PatchPolicy, now time.Time
n, err := deps.CountTargets(p)
if err != nil {
recordSkip(ctx, deps, p, "error: "+err.Error(), due, now)
retryLater(ctx, deps, p, err, due, next, now)
return
}
active, err := hasActiveRun(ctx, p)
if err != nil {
recordSkip(ctx, deps, p, "error: "+err.Error(), due, now)
retryLater(ctx, deps, p, err, due, next, now)
return
}
switch d := Decide(due, end, now, active, n); d {
case Fire:
if err := deps.StartPolicyRun(p, end); err != nil {
recordSkip(ctx, deps, p, "error: "+err.Error(), due, now)
retryLater(ctx, deps, p, err, due, next, now)
return
}
_, _ = db.Col("patch_policies").UpdateOne(ctx, bson.M{"policy_id": p.PolicyID},
@@ -130,6 +130,19 @@ func hasActiveRun(ctx context.Context, p models.PatchPolicy) (bool, error) {
return false, err
}
// retryLater handles an error after the claim. The claim is put back, guarded
// on the value just written so a concurrent edit to the policy is not undone,
// and the next tick retries the same occurrence. Decide's missed rule bounds
// the retries: once the occurrence is too late it is skipped as missed.
func retryLater(ctx context.Context, deps Deps, p models.PatchPolicy, cause error, due, next, now time.Time) {
if _, err := db.Col("patch_policies").UpdateOne(ctx,
bson.M{"policy_id": p.PolicyID, "next_run_at": next},
bson.M{"$set": bson.M{"next_run_at": due}}); err != nil {
log.Printf("patchsched: policy %s: put back claim: %v", p.PolicyID, err)
}
recordSkip(ctx, deps, p, "error: "+cause.Error(), due, now)
}
func recordSkip(ctx context.Context, deps Deps, p models.PatchPolicy, reason string, due, at time.Time) {
_, _ = db.Col("patch_policies").UpdateOne(ctx, bson.M{"policy_id": p.PolicyID},
bson.M{"$set": bson.M{"last_skipped": models.Skip{Reason: reason, Due: due, At: at}}})