feat(docker): restore failed Compose updates and retain recent releases
CI / check (push) Successful in 1m7s
CI / deploy (push) Successful in 1m20s

This commit is contained in:
Simo committed 2026-09-07 22:10:13 +02:00
1 parent fe1ee8a6fe
commit 745d0b7247
4 files changed
+159 -16

No files matched your search

+34
View File
@@ -29,6 +29,11 @@ function simulate(scenario: string) {
const dir = mkdtempSync(join(tmpdir(), "cms-compose-test-"));
try {
mkdirSync(join(dir, "scripts"));
mkdirSync(join(dir, "logs"));
writeFileSync(
join(dir, "logs/docker-release-history.log"),
`${"b".repeat(40)}\n${"c".repeat(40)}\n`,
);
copyFileSync(
resolve(root, "scripts/docker-update.sh"),
join(dir, "scripts/docker-update.sh"),
@@ -50,6 +55,7 @@ function simulate(scenario: string) {
if (result.error) throw result.error;
return {
status: result.status,
restored: existsSync(join(dir, "restored")),
output: result.stdout + result.stderr,
calls: existsSync(join(dir, "calls"))
? readFileSync(join(dir, "calls"), "utf8")
@@ -60,6 +66,32 @@ function simulate(scenario: string) {
}
}
describe("Docker clone updates", () => {
it("removes only historical release tags beyond the current and previous", () => {
const r = simulate("success");
expect(r.calls).toContain(`docker image rm epicnext-cms:${"c".repeat(40)}`);
expect(r.calls).not.toContain(
`docker image rm epicnext-cms:${"b".repeat(40)}`,
);
});
it("preserves older images used by another container", () => {
const r = simulate("retained-in-use");
expect(r.status, r.output).toBe(0);
expect(r.calls).not.toContain(
`docker image rm epicnext-cms:${"c".repeat(40)}`,
);
});
it("reports an unsuccessful rollback and retains the recovery image", () => {
const r = simulate("rollback-failure");
expect(r.status).not.toBe(0);
expect(r.output).toContain("rollback could not recreate CMS");
expect(r.calls).not.toContain("docker image rm epicnext-cms:rollback-");
});
it("handles first installation failures without claiming rollback", () => {
const r = simulate("first-failure");
expect(r.status).not.toBe(0);
expect(r.restored).toBe(false);
expect(r.output).toContain("no previous image exists");
});
it("builds the pulled commit, migrates before recreation and verifies local/public HTTP", () => {
const r = simulate("success");
expect(r.status, r.output).toBe(0);
@@ -91,6 +123,8 @@ describe("Docker clone updates", () => {
const r = simulate(scenario);
expect(r.status, r.output).not.toBe(0);
expect(r.output).not.toContain("Verified release");
expect(r.restored).toBe(true);
expect(r.output).toContain("Rollback verified locally");
},
);
});
+15 -2
View File
@@ -15,15 +15,28 @@ docker() {
case "$1 ${2:-}" in
'inspect --format')
if [ "${@: -1}" = epicnext-cms-app ]; then [ "$SCENARIO" = ci-active ] && echo true; return 0; fi
if [ "${@: -1}" = previous123 ] || [ -f "$TEST_DIR/restored" ]; then echo sha256:old; return 0; fi
if [ "$SCENARIO" = wrong-image ]; then echo sha256:old; else echo sha256:new; fi ;;
'image inspect')
if [[ "$*" = *org.opencontainers* ]] && [[ "$*" = *sha256:old* ]]; then printf 'b%.0s' {1..40}; echo; return 0; fi
if [[ "$*" = *org.opencontainers* ]]; then echo "$TEST_SHA"; else echo sha256:new; fi ;;
'compose config') return 0 ;;
'compose build') [ "$SCENARIO" != build-failure ] ;;
'compose up') [ "$SCENARIO" != recreate-failure ] ;;
'compose ps') echo container123 ;;
'compose up')
if [[ "$CMS_RELEASE" = rollback-* ]]; then
[ "$SCENARIO" != rollback-failure ] || return 1
touch "$TEST_DIR/restored"; return 0
fi
touch "$TEST_DIR/cutover"
[ "$SCENARIO" != recreate-failure ] ;;
'compose ps')
if [ -f "$TEST_DIR/cutover" ]; then echo container123
elif [ "$SCENARIO" != first-failure ]; then echo previous123; fi ;;
'run --rm') [ "$SCENARIO" != migration-failure ] ;;
'ps -aq') if [ "$SCENARIO" = retained-in-use ]; then echo other-container; fi ;;
'exec container123')
if [ -f "$TEST_DIR/restored" ]; then return 0; fi
if [ "$SCENARIO" = first-failure ] || [ "$SCENARIO" = rollback-failure ]; then return 1; fi
if [ "$SCENARIO" = wrong-release ]; then return 1; fi
if [ "$SCENARIO" = wrong-public ] && [[ "$*" = *example.test* ]]; then return 1; fi ;;
*) return 0 ;;