fix(patching): final review fixes
- 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:
@@ -17,6 +17,17 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"api.ApplyUpdatesErrorResponse": {
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"run_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"api.ApplyUpdatesResponse": {
|
||||
"properties": {
|
||||
"message": {
|
||||
@@ -1077,6 +1088,10 @@
|
||||
},
|
||||
"models.Inventory": {
|
||||
"properties": {
|
||||
"boot_time": {
|
||||
"description": "BootTime is the host's last reported boot time, stored on every report\nthat carries one so a patch reboot can be proven by a changed boot.",
|
||||
"type": "string"
|
||||
},
|
||||
"cpu": {
|
||||
"$ref": "#/components/schemas/models.CPUInfo"
|
||||
},
|
||||
@@ -7203,11 +7218,11 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/api.ApplyUpdatesResponse"
|
||||
"$ref": "#/components/schemas/api.ApplyUpdatesErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Service Unavailable"
|
||||
"description": "agent offline; the attempt is recorded as run_id"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
||||
@@ -756,7 +756,7 @@ func updateAgent(c *gin.Context) {
|
||||
// @Param source query string false "vulnerabilities when started from the vulnerabilities page"
|
||||
// @Success 202 {object} ApplyUpdatesResponse
|
||||
// @Failure 404 {object} ErrorResponse
|
||||
// @Failure 503 {object} ApplyUpdatesResponse
|
||||
// @Failure 503 {object} ApplyUpdatesErrorResponse "agent offline; the attempt is recorded as run_id"
|
||||
// @Security cookieAuth
|
||||
// @Security bearerAuth
|
||||
// @Router /servers/{id}/apply-updates [post]
|
||||
|
||||
@@ -170,6 +170,10 @@ func updateWindow(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := services.CheckWindowScope(auth.InstanceID(c), c.Param("id"), auth.ServerScope(c)); err != nil {
|
||||
patchError(c, err)
|
||||
return
|
||||
}
|
||||
w, err := services.UpdateWindow(auth.InstanceID(c), c.Param("id"), body)
|
||||
if err != nil {
|
||||
patchError(c, err)
|
||||
@@ -192,6 +196,10 @@ func updateWindow(c *gin.Context) {
|
||||
// @Security bearerAuth
|
||||
// @Router /maintenance-windows/{id} [delete]
|
||||
func deleteWindow(c *gin.Context) {
|
||||
if err := services.CheckWindowScope(auth.InstanceID(c), c.Param("id"), auth.ServerScope(c)); err != nil {
|
||||
patchError(c, err)
|
||||
return
|
||||
}
|
||||
if err := services.DeleteWindow(auth.InstanceID(c), c.Param("id")); err != nil {
|
||||
patchError(c, err)
|
||||
return
|
||||
|
||||
@@ -400,13 +400,16 @@ var serverScopedRoutes = map[string]scopeDecl{
|
||||
"GET /api/mcp": exempt,
|
||||
|
||||
// Maintenance windows are a cron expression, a zone and a duration. They
|
||||
// name no server and return no server data.
|
||||
// name no server and return no server data, so reading and creating one
|
||||
// is exempt. Changing or deleting one moves or stops the patching of
|
||||
// every policy using it, so each is refused when any of those policies
|
||||
// targets servers outside the token's tag restriction.
|
||||
"GET /api/maintenance-windows": exempt,
|
||||
"POST /api/maintenance-windows": exempt,
|
||||
"POST /api/maintenance-windows/preview": exempt,
|
||||
"GET /api/maintenance-windows/:id": exempt,
|
||||
"PUT /api/maintenance-windows/:id": exempt,
|
||||
"DELETE /api/maintenance-windows/:id": exempt,
|
||||
"PUT /api/maintenance-windows/:id": scoped,
|
||||
"DELETE /api/maintenance-windows/:id": scoped,
|
||||
// Reading a policy returns its selector (server IDs and tag pairs) and no
|
||||
// hostname, inventory or state, the same data a workflow's targets carry.
|
||||
"GET /api/patch-policies": exempt,
|
||||
|
||||
@@ -307,6 +307,13 @@ type ApplyUpdatesResponse struct {
|
||||
RunID string `json:"run_id,omitempty"`
|
||||
}
|
||||
|
||||
// ApplyUpdatesErrorResponse is the 503 body of apply-updates: the agent is
|
||||
// offline, and the attempt is still recorded as a run.
|
||||
type ApplyUpdatesErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
RunID string `json:"run_id,omitempty"`
|
||||
}
|
||||
|
||||
// WindowPreviewRequest is the body of POST /maintenance-windows/preview.
|
||||
type WindowPreviewRequest struct {
|
||||
Cron string `json:"cron"`
|
||||
|
||||
Reference in New Issue
Block a user