mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-24 17:36:42 +00:00
feat: add service-scoped Compose update and restore (#1648)
* feat: add service-scoped Compose update and restore Allow updating or rebuilding one declared Compose service on multi-service stacks without recreating siblings, with recovery snapshots, health-gate observation, and prune holds for rollback images. Full-stack update paths and single-service UX stay unchanged. * fix: sanitize service-scoped update log messages for CodeQL * fix: address service-scoped update audit findings B-01 through B-07 * fix: complete service-scoped update audit metadata and surfaces * test: wrap Updates readiness tests for deploy-feedback context * fix: keep service recovery reachable without Deploy Progress Make failed service-gate recovery discoverable when Deploy Progress is disabled or dismissed, suppress stale image-scan notification side effects, normalize ComposeService line endings, and add focused regression coverage. * fix: resurface ContainersHealth density and expand on multi-service stacks Service grouping hid the summary strip and Compact/Detailed/Expand controls that still applied to multi-container stacks.
This commit is contained in:
+270
-2
@@ -1772,10 +1772,21 @@ paths:
|
||||
now, from the stored preflight result, open drift findings, live
|
||||
container health, the pending image change, the rollback backup slot,
|
||||
and node disk headroom. Advisory only: no verdict blocks an update.
|
||||
Requires `stack:read` permission.
|
||||
Requires `stack:read` permission. Pass `service` to scope the verdict
|
||||
to one Compose service on a multi-service stack.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/stackName"
|
||||
- $ref: "#/components/parameters/nodeId"
|
||||
- name: service
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
description: |
|
||||
When set, readiness is scoped to this Compose service (multi-service
|
||||
stacks only). Stack-wide guardrails still apply; sibling and
|
||||
dependency signals become advisory. Single-service stacks reject
|
||||
this parameter with 400.
|
||||
responses:
|
||||
"200":
|
||||
description: Readiness report.
|
||||
@@ -1810,6 +1821,12 @@ paths:
|
||||
type: string
|
||||
affectsVerdict:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Invalid service scope.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
@@ -1817,6 +1834,238 @@ paths:
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/stacks/{stackName}/effective-services:
|
||||
get:
|
||||
operationId: getStackEffectiveServices
|
||||
tags: [Stacks]
|
||||
summary: List effective Compose services
|
||||
description: |
|
||||
Returns the fail-closed effective service model for the stack (declared
|
||||
image, build presence, expected replicas, depends_on, healthcheck).
|
||||
Requires `stack:read` permission.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/stackName"
|
||||
- $ref: "#/components/parameters/nodeId"
|
||||
responses:
|
||||
"200":
|
||||
description: Effective model result.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [renderable]
|
||||
properties:
|
||||
renderable:
|
||||
type: boolean
|
||||
services:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [name, declaredImage, hasBuild, expectedReplicas, dependsOn, hasHealthcheck]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
declaredImage:
|
||||
type: string
|
||||
nullable: true
|
||||
hasBuild:
|
||||
type: boolean
|
||||
expectedReplicas:
|
||||
type: integer
|
||||
dependsOn:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
hasHealthcheck:
|
||||
type: boolean
|
||||
code:
|
||||
type: string
|
||||
error:
|
||||
type: string
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/stacks/{stackName}/services/{serviceName}/update:
|
||||
post:
|
||||
operationId: updateStackService
|
||||
tags: [Stacks]
|
||||
summary: Update or rebuild one Compose service
|
||||
description: |
|
||||
Manually pulls or builds one declared service, then recreates only that
|
||||
service with `--no-deps --force-recreate`. Multi-service stacks only.
|
||||
Requires `stack:deploy`. Automation paths continue to update the full stack.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/stackName"
|
||||
- name: serviceName
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/nodeId"
|
||||
responses:
|
||||
"200":
|
||||
description: Service update completed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [serviceName, healthGateId, observing, recoveryAvailable]
|
||||
properties:
|
||||
serviceName:
|
||||
type: string
|
||||
healthGateId:
|
||||
type: string
|
||||
nullable: true
|
||||
observing:
|
||||
type: boolean
|
||||
recoveryId:
|
||||
type: string
|
||||
nullable: true
|
||||
recoveryAvailable:
|
||||
type: boolean
|
||||
recheckWarning:
|
||||
type: string
|
||||
"400":
|
||||
description: Service update rejected (single-service stack, missing service, or unavailable capability).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
description: Service update failed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/api/stacks/{stackName}/services/{serviceName}/recovery:
|
||||
get:
|
||||
operationId: getStackServiceRecovery
|
||||
tags: [Stacks]
|
||||
summary: Latest active service recovery snapshot
|
||||
description: |
|
||||
Returns the newest active, unexpired recovery row for one service, or
|
||||
`recovery: null` when none is available. Used so Restore stays
|
||||
discoverable when Deploy Progress is disabled or dismissed.
|
||||
Requires `stack:deploy`.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/stackName"
|
||||
- name: serviceName
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/nodeId"
|
||||
responses:
|
||||
"200":
|
||||
description: Recovery lookup completed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [recovery]
|
||||
properties:
|
||||
recovery:
|
||||
oneOf:
|
||||
- type: "null"
|
||||
- type: object
|
||||
required: [id, status, expiresAt, createdAt]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
healthGateId:
|
||||
type: string
|
||||
nullable: true
|
||||
expiresAt:
|
||||
type: integer
|
||||
createdAt:
|
||||
type: integer
|
||||
majorityImageId:
|
||||
type: string
|
||||
declaredImageRef:
|
||||
type: string
|
||||
"400":
|
||||
description: Capability unavailable.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
/api/stacks/{stackName}/services/{serviceName}/restore:
|
||||
post:
|
||||
operationId: restoreStackService
|
||||
tags: [Stacks]
|
||||
summary: Restore one Compose service from a recovery snapshot
|
||||
description: |
|
||||
Retags the captured majority image onto the declared tag and recreates
|
||||
only that service. Body must include `recoveryId` from a prior service
|
||||
update. Requires `stack:deploy`.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/stackName"
|
||||
- name: serviceName
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- $ref: "#/components/parameters/nodeId"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [recoveryId]
|
||||
properties:
|
||||
recoveryId:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Service restore completed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [serviceName, healthGateId, observing, recoveryAvailable]
|
||||
properties:
|
||||
serviceName:
|
||||
type: string
|
||||
healthGateId:
|
||||
type: string
|
||||
nullable: true
|
||||
observing:
|
||||
type: boolean
|
||||
recoveryId:
|
||||
type: string
|
||||
nullable: true
|
||||
recoveryAvailable:
|
||||
type: boolean
|
||||
"400":
|
||||
description: Restore rejected (missing recoveryId or invalid binding).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
description: Service restore failed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/api/stacks/{stackName}/rollback-readiness:
|
||||
get:
|
||||
operationId: getStackRollbackReadiness
|
||||
@@ -1908,7 +2157,7 @@ paths:
|
||||
trigger:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [update, deploy, null]
|
||||
enum: [update, deploy, service_update, service_restore, null]
|
||||
reason:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -1937,6 +2186,25 @@ paths:
|
||||
nullable: true
|
||||
restarts:
|
||||
type: integer
|
||||
service:
|
||||
type: string
|
||||
nullable: true
|
||||
role:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [primary, collateral, null]
|
||||
targetScope:
|
||||
type: string
|
||||
enum: [stack, service]
|
||||
description: Defaults to stack for legacy runs.
|
||||
serviceName:
|
||||
type: string
|
||||
nullable: true
|
||||
failureSource:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [primary, collateral, null]
|
||||
description: Set on failed service-scoped runs; null for stack and successful runs.
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
|
||||
Reference in New Issue
Block a user