feat: patching REST API, patching scope, run IDs from apply-updates and MCP

This commit is contained in:
2026-09-15 09:31:51 +00:00
parent 17c9f813fc
commit b5bbf28c63
11 changed files with 1697 additions and 28 deletions
@@ -191,3 +191,33 @@ func CountPolicyTargets(p models.PatchPolicy) (int, error) {
}
return len(servers), err
}
var ErrPatchRunActive = errors.New("a run of this policy is already in progress")
// CheckPolicyScope refuses a tag-restricted token acting on a policy whose
// targets reach outside its restriction, with the workflow rule unchanged.
func CheckPolicyScope(instanceID string, p models.PatchPolicy, tokenScope map[string]string) error {
return validateWorkflowTargetScope(instanceID, p.TargetServerIDs, p.TargetTags, tokenScope)
}
// StartRunNow opens a window of the policy's usual length starting now. It is
// how an operator tests a policy on a Tuesday afternoon.
func StartRunNow(instanceID, policyID, actor string, tokenScope map[string]string) (*models.PatchRun, error) {
p, err := GetPolicy(instanceID, policyID)
if err != nil {
return nil, err
}
if err := CheckPolicyScope(instanceID, *p, tokenScope); err != nil {
return nil, err
}
w, err := GetWindow(instanceID, p.WindowID)
if err != nil {
return nil, err
}
ctx, cancel := patchCtx()
defer cancel()
if db.Col("patch_runs").FindOne(ctx, bson.M{"instance_id": instanceID, "policy_id": policyID, "status": models.PatchRunRunning}).Err() == nil {
return nil, ErrPatchRunActive
}
return StartPolicyRun(*p, patchsched.WindowEnd(time.Now(), w.DurationMinutes), models.PatchSourceRunNow, actor)
}
+3 -2
View File
@@ -11,8 +11,8 @@ import (
// the vocabulary below.
var ErrInvalidScope = errors.New("invalid scope")
// ScopeResources is the whole vocabulary. Ten resources, each with :read and
// :write, and write implies read on the same resource.
// ScopeResources is the whole vocabulary. Eleven resources, each with :read
// and :write, and write implies read on the same resource.
//
// It is deliberately coarse. A scope per endpoint is a table nobody maintains,
// and a route added without an entry either fails closed and breaks, or
@@ -27,6 +27,7 @@ var ScopeResources = []string{
"workloads",
"settings",
"status",
"patching",
// mcp:read is permission to reach the MCP endpoint at all; mcp:write is
// permission for its write tools, which are not merely refused without it
// but omitted from tools/list entirely.