fix(self-recreate): capture built image SHA via docker images tag, normalize SHA comparison

The prior expected_image_sha was captured via docker compose images, which returns the image of the existing (pre-recreate) container — not the freshly-built one. Switch to docker images ${project}-${service}:latest --quiet --no-trunc which returns the post-build image SHA. Also normalize sha256: prefix in completeSelfRecreate comparison so docker compose images output (sha256-prefixed) and docker inspect output (also sha256-prefixed) match cleanly.
This commit is contained in:
kua-deploy-split 2026-05-21 18:35:37 -04:00
parent 06852c227c
commit db647bcd0a
1 changed files with 8 additions and 2 deletions

View File

@ -708,7 +708,11 @@ ${detail}`);
const selfRecreate = appName === SELF_APP_NAME && isLocal(server) && stateless.includes(SELF_APP_NAME); const selfRecreate = appName === SELF_APP_NAME && isLocal(server) && stateless.includes(SELF_APP_NAME);
if (selfRecreate) { if (selfRecreate) {
// Capture the freshly-built image SHA for post-restart verification. // Capture the freshly-built image SHA for post-restart verification.
const builtSha = (await run(`docker compose -p ${composeProject} -f ${deployDir}/docker-compose.yml images --quiet ${SELF_APP_NAME} 2>/dev/null | head -1`)).stdout.trim() || null; // `docker compose images` returns the image used by the EXISTING container
// (still the OLD one before recreate). For the just-built image, query the
// image tag that compose builds into: ${project}-${service}:latest.
const builtImageTag = `${composeProject}-${SELF_APP_NAME}:latest`;
const builtSha = (await run(`docker images ${builtImageTag} --quiet --no-trunc | head -1`)).stdout.trim() || null;
steps[steps.length - 1] = { steps[steps.length - 1] = {
step: 'deploy', step: 'deploy',
status: 'running', status: 'running',
@ -1443,7 +1447,9 @@ async function completeSelfRecreate() {
} }
} catch { /* docker unreachable — leave progress in pending; next startup retries */ } } catch { /* docker unreachable — leave progress in pending; next startup retries */ }
const imageMatches = expectedSha && runningSha && expectedSha.endsWith(runningSha.replace(/^sha256:/, '')); // Normalize: strip sha256: prefix from both sides for tolerant comparison.
const normSha = s => (s || '').replace(/^sha256:/, '').trim();
const imageMatches = expectedSha && runningSha && normSha(expectedSha) === normSha(runningSha);
const freshlyStarted = startedAtStr && recreateStartedAt && new Date(startedAtStr) >= recreateStartedAt; const freshlyStarted = startedAtStr && recreateStartedAt && new Date(startedAtStr) >= recreateStartedAt;
const ok = !!(imageMatches && freshlyStarted && state === 'running'); const ok = !!(imageMatches && freshlyStarted && state === 'running');