mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-30 12:09:15 +00:00
fix(fleet): verify update status before removing readiness cards (#1697)
* fix(fleet): verify update status before removing readiness cards Full-stack Apply now rechecks persisted status after the health gate starts, reloads the live preview before dropping a card, and invalidates the hub fleet aggregation so cleared updates cannot resurrect from a stale cache. Closes #1686 * fix(fleet): align persisted update status with preview semver detection Share digest-plus-tag detection so post-Apply sidebar status matches Fleet and Anatomy. * fix(fleet): keep tag-only updates advisory for Compose automation Expose digestUpdate vs tagUpdate from checkImage so scheduled and API auto-update only apply same-tag digest drift Compose can pull. * docs: clarify scheduled auto-update applies digest drift only Document that higher pinned tags stay advisory until Compose is changed, matching schedule and Run Now behavior. * docs: require Compose pin edits for higher-tag advisories Stop recommending Apply now or Update as remedies that cannot rewrite a pinned image tag. * docs: clarify Apply now pulls pinned tags only Align the detection-cadence bullet with digest-rebuild vs higher-tag guidance. * fix(fleet): keep tag advisories after apply and scheduled updates Tag-only previews were treated as cleared on Fleet reload, and scheduled/ Run Now paths wiped status without rechecking. Align post-update verification with the manual Apply path (health gate first, recheck, no blind clear) and block digest apply when sibling image checks failed. * fix(fleet): clear eslint unused-arg and containers assignment
This commit is contained in:
@@ -10,12 +10,15 @@ import { StackOpLockService, stackOpSkipMessage as skipMessage } from './StackOp
|
||||
import { FileSystemService } from './FileSystemService';
|
||||
import { HealthGateService } from './HealthGateService';
|
||||
import { ServiceUpdateRecoveryService } from './ServiceUpdateRecoveryService';
|
||||
import { ImageUpdateService } from './ImageUpdateService';
|
||||
import {
|
||||
createAutoUpdateDigestGateState,
|
||||
messageWhenDigestApplyBlockedByCheckErrors,
|
||||
messageWhenNoDigestUpdate,
|
||||
recordAutoUpdateImageCheck,
|
||||
} from '../helpers/autoUpdateDigestGate';
|
||||
import { ImageUpdateService, UPDATE_VERIFICATION_INCOMPLETE_WARNING } from './ImageUpdateService';
|
||||
import { invalidateFleetUpdateCache } from '../helpers/fleetUpdateCache';
|
||||
import { invalidateNodeCaches } from '../helpers/cacheInvalidation';
|
||||
import { isDebugEnabled } from '../utils/debug';
|
||||
import { getErrorMessage } from '../utils/errors';
|
||||
import { formatNoTargetError } from '../utils/remoteTarget';
|
||||
@@ -770,14 +773,13 @@ export class SchedulerService {
|
||||
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 results: string[] = [];
|
||||
|
||||
for (const stackName of stackNames) {
|
||||
try {
|
||||
const output = await this.executeUpdateForStack(stackName, task.node_id, docker, imageUpdateService, db, isFleet || isWildcard);
|
||||
const output = await this.executeUpdateForStack(stackName, task.node_id, docker, imageUpdateService, isFleet || isWildcard);
|
||||
results.push(output);
|
||||
} catch (e) {
|
||||
const msg = getErrorMessage(e, String(e));
|
||||
@@ -1034,7 +1036,6 @@ export class SchedulerService {
|
||||
nodeId: number,
|
||||
docker: DockerController,
|
||||
imageUpdateService: ImageUpdateService,
|
||||
db: DatabaseService,
|
||||
isWildcard = false
|
||||
): Promise<string> {
|
||||
const containers = await docker.getContainersByStack(stackName);
|
||||
@@ -1076,6 +1077,8 @@ export class SchedulerService {
|
||||
if (!gate.hasDigestUpdate) {
|
||||
return messageWhenNoDigestUpdate(stackName, gate, imageRefs.length);
|
||||
}
|
||||
const checkErrorBlock = messageWhenDigestApplyBlockedByCheckErrors(stackName, gate);
|
||||
if (checkErrorBlock) return checkErrorBlock;
|
||||
|
||||
const { updatedImages } = gate;
|
||||
|
||||
@@ -1096,7 +1099,9 @@ export class SchedulerService {
|
||||
),
|
||||
);
|
||||
if (!lock.ran) return skipMessage(stackName, lock.existing.action);
|
||||
db.clearStackUpdateStatus(nodeId, stackName);
|
||||
|
||||
// Health observation starts immediately after Compose; registry recheck is
|
||||
// isolated so a verification failure cannot turn Compose success into a failure.
|
||||
const healthGateId = HealthGateService.getInstance().beginStack(nodeId, stackName, 'update', 'system:scheduler');
|
||||
const orchResult = lock.result;
|
||||
const recoveryId = orchResult && orchResult.kind === 'stack_compose_done' ? orchResult.recoveryId : null;
|
||||
@@ -1105,6 +1110,30 @@ export class SchedulerService {
|
||||
StackUpdateRecoveryService.getInstance().linkGateOrRetain(recoveryId, healthGateId);
|
||||
}
|
||||
|
||||
// Recheck persists digest-cleared / tag-advisory state. Do not blind-clear.
|
||||
let recheckWarning: string | undefined;
|
||||
try {
|
||||
const recheck = await imageUpdateService.recheckStack(nodeId, stackName);
|
||||
if (recheck.warning) recheckWarning = recheck.warning;
|
||||
} catch (recheckErr) {
|
||||
console.warn(
|
||||
`[SchedulerService] Post-update recheck failed for ${sanitizeForLog(stackName)}:`,
|
||||
sanitizeForLog(getErrorMessage(recheckErr, 'unknown')),
|
||||
);
|
||||
recheckWarning = UPDATE_VERIFICATION_INCOMPLETE_WARNING;
|
||||
}
|
||||
|
||||
invalidateFleetUpdateCache();
|
||||
invalidateNodeCaches(nodeId);
|
||||
NotificationService.getInstance().broadcastEvent({
|
||||
type: 'state-invalidate',
|
||||
scope: 'image-updates',
|
||||
nodeId,
|
||||
stackName,
|
||||
action: 'stack-updated',
|
||||
ts: Date.now(),
|
||||
});
|
||||
|
||||
this.safeDispatch(
|
||||
'info',
|
||||
'image_update_applied',
|
||||
@@ -1112,7 +1141,8 @@ export class SchedulerService {
|
||||
stackName
|
||||
);
|
||||
|
||||
return `Stack "${stackName}": updated (${updatedImages.join(', ')}).`;
|
||||
const base = `Stack "${stackName}": updated (${updatedImages.join(', ')}).`;
|
||||
return recheckWarning ? `${base} ${recheckWarning}` : base;
|
||||
}
|
||||
|
||||
private async executeScan(task: ScheduledTask): Promise<{ output: string; failed: number }> {
|
||||
|
||||
Reference in New Issue
Block a user