force-recreate the stack after an in-place restore so it comes up on the restored data
This commit is contained in:
2 files changed
+56
-1
No files matched your search
@@ -2430,7 +2430,12 @@ export async function redeployStackFromDir(
|
||||
composePath,
|
||||
envPath: hasEnv ? envPath : undefined,
|
||||
composeFileName,
|
||||
stackFiles
|
||||
stackFiles,
|
||||
// A restore rewrote the stack dir and swapped the volume data underneath the
|
||||
// stack. Force-recreate so the container is rebuilt fresh against the restored
|
||||
// state; a plain `up` sees the unchanged compose and only restarts the stopped
|
||||
// container, which can leave it not-yet-running after an in-place swap.
|
||||
forceRecreate: true
|
||||
},
|
||||
composeContent,
|
||||
envVars,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Regression guard: the restore redeploy MUST force-recreate.
|
||||
*
|
||||
* redeployStackFromDir() is the redeploy-from-restored-snapshot path - its ONLY
|
||||
* caller is the backup restore port (backups/index.ts), used by both the in-place
|
||||
* and clone restore paths. A restore rewrites the stack dir and swaps the
|
||||
* volume data underneath the stack, then this function runs `docker compose up`. A
|
||||
* plain `up` sees the unchanged compose and only restarts the container compose
|
||||
* thinks is up-to-date, which after an in-place swap can leave it not-yet-running
|
||||
* (the source of an intermittent post-restore "stack never came up"). Passing
|
||||
* forceRecreate rebuilds the container fresh against the restored state.
|
||||
*
|
||||
* This asserts at the source level (like tests/deploy-stack-online.test.ts) that the
|
||||
* executeComposeCommand('up', ...) inside redeployStackFromDir carries
|
||||
* forceRecreate: true - a real daemon isn't needed to prove the invariant, and it
|
||||
* survives refactors of the surrounding code.
|
||||
*/
|
||||
// @ts-expect-error -- bun:test is a runtime built-in with no types installed
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const stacksSrc = readFileSync(join(here, '..', 'src', 'lib', 'server', 'stacks.ts'), 'utf8');
|
||||
|
||||
// The body of redeployStackFromDir, from its declaration to the next top-level
|
||||
// `export ... function` (paren/brace matching would be overkill - the next export
|
||||
// is a hard boundary).
|
||||
function redeployStackFromDirBody(source: string): string {
|
||||
const start = source.indexOf('export async function redeployStackFromDir');
|
||||
expect(start).toBeGreaterThan(-1);
|
||||
const rest = source.slice(start + 1);
|
||||
const nextExport = rest.indexOf('\nexport ');
|
||||
return rest.slice(0, nextExport === -1 ? undefined : nextExport);
|
||||
}
|
||||
|
||||
describe('restore redeploy force-recreates', () => {
|
||||
const body = redeployStackFromDirBody(stacksSrc);
|
||||
|
||||
test('redeployStackFromDir runs an `up`', () => {
|
||||
expect(body).toContain("executeComposeCommand(");
|
||||
expect(body).toContain("'up'");
|
||||
});
|
||||
|
||||
test('redeployStackFromDir passes forceRecreate: true', () => {
|
||||
// Whitespace-tolerant: `forceRecreate: true` in the options object.
|
||||
expect(body).toMatch(/forceRecreate\s*:\s*true/);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user