mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-20 23:32:19 +00:00
feat(scheduler): support fleet-wide auto-update schedules per node (#773)
Allow a scheduled task with action='update' and target_type='fleet' to update every eligible stack on a node in a single schedule entry. The executor respects each stack's per-stack auto-update policy via a single batch query, skipping stacks that have opted out. For remote nodes the request proxies to the remote Sencho instance, which already enforces the same policy in its /api/auto-update/execute endpoint. Backend route validation now accepts update+fleet as a valid combo (previously only update+stack was allowed) and requires node_id. Frontend adds an "Auto-update All Stacks" option to the scheduled-task creation form with a node selector and descriptive helper text.
This commit is contained in:
@@ -482,41 +482,56 @@ export class SchedulerService {
|
||||
}
|
||||
|
||||
private async executeUpdate(task: ScheduledTask): Promise<string> {
|
||||
if (!task.target_id || task.node_id == null) {
|
||||
throw new Error('Auto-update requires target_id (stack name or "*") and node_id');
|
||||
if (task.node_id == null) {
|
||||
throw new Error('Auto-update requires node_id');
|
||||
}
|
||||
|
||||
// For remote nodes, proxy the entire execution to the remote Sencho instance
|
||||
const isFleet = task.target_type === 'fleet';
|
||||
|
||||
if (!isFleet && !task.target_id) {
|
||||
throw new Error('Auto-update requires target_id (stack name or "*")');
|
||||
}
|
||||
|
||||
// For remote nodes, proxy the entire execution to the remote Sencho instance.
|
||||
// The remote /api/auto-update/execute endpoint already handles per-stack
|
||||
// auto-update policy, so passing '*' for fleet is sufficient.
|
||||
const node = NodeRegistry.getInstance().getNode(task.node_id);
|
||||
if (node?.type === 'remote') {
|
||||
return this.executeUpdateRemote(task.node_id, task.target_id);
|
||||
return this.executeUpdateRemote(task.node_id, isFleet ? '*' : task.target_id!);
|
||||
}
|
||||
|
||||
// Local node: execute directly
|
||||
const isWildcard = task.target_id === '*';
|
||||
let stackNames: string[];
|
||||
if (isWildcard) {
|
||||
if (isFleet || isWildcard) {
|
||||
stackNames = await FileSystemService.getInstance(task.node_id).getStacks();
|
||||
if (stackNames.length === 0) {
|
||||
return 'No stacks found on node; skipped.';
|
||||
}
|
||||
} else {
|
||||
stackNames = [task.target_id];
|
||||
stackNames = [task.target_id!];
|
||||
}
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
console.log(`[SchedulerService] executeUpdate: ${stackNames.length} stack(s) to check, wildcard=${isWildcard}`);
|
||||
console.log(`[SchedulerService] executeUpdate: ${stackNames.length} stack(s) to check, fleet=${isFleet}, wildcard=${isWildcard}`);
|
||||
}
|
||||
|
||||
const db = DatabaseService.getInstance();
|
||||
const docker = DockerController.getInstance(task.node_id);
|
||||
const imageUpdateService = ImageUpdateService.getInstance();
|
||||
const compose = ComposeService.getInstance(task.node_id);
|
||||
const db = DatabaseService.getInstance();
|
||||
const results: string[] = [];
|
||||
|
||||
// Single batch query for fleet mode; per-stack default is enabled (true) when no explicit row exists.
|
||||
const policyMap = isFleet ? db.getStackAutoUpdateSettingsForNode(task.node_id) : null;
|
||||
|
||||
for (const stackName of stackNames) {
|
||||
try {
|
||||
const output = await this.executeUpdateForStack(stackName, task.node_id ?? 0, docker, imageUpdateService, compose, db, isWildcard);
|
||||
if (isFleet && (policyMap![stackName] ?? true) === false) {
|
||||
results.push(`Stack "${stackName}": auto-updates disabled; skipped.`);
|
||||
continue;
|
||||
}
|
||||
const output = await this.executeUpdateForStack(stackName, task.node_id, docker, imageUpdateService, compose, db, isFleet || isWildcard);
|
||||
results.push(output);
|
||||
} catch (e) {
|
||||
const msg = getErrorMessage(e, String(e));
|
||||
|
||||
Reference in New Issue
Block a user