feat(db): snapshot and restore the shared Postgres (spec 15) - #100
Merged
Conversation
Graduate the spec-15 data-lifecycle verbs `db snapshot [name]`, `db snapshot ls`, and `db restore <name>` (thin v2 scope: Postgres-only, per-project tenant). - New internal/db seam: a Dumper interface + PgDumper that shells pg_dump/pg_restore/psql behind an injectable Runner (release binary stays CGO-free; password rides PGPASSWORD in env, never the argv). Preflight maps a missing client tool to a one-line remediation. - internal/orchestrate/snapshot.go reuses the exact provision host-reachability pattern (engineTarget → FreeHostPort + writeProvisionOverlay + compose up on the shared stack) to reach the warm Postgres over a ledger-allocated 127.0.0.1 host port WITHOUT publishing a permanent one. The dump/restore process runs OUTSIDE the flock; only the ledger row + event write are locked. - Dumps + a sidecar land under $DEVSTACK_HOME/snapshots/<workspace>/<name>.dump (store.SnapshotsPath); each snapshot is a provisioned(kind=snapshot) ledger row + a db.snapshot/db.restore event (free-text kind → no migration). - restore refuses a non-empty tenant without --force, confirms (or --yes) for destructive replay, re-hashes the dump for integrity, and honors the --project/--db/--instance selectors. --json contract throughout. Tests: snapshot→restore round-trip via a mock runner (asserts pg_dump/pg_restore argv + PGPASSWORD-in-env + the host-port overlay allocation + the ledger row), restore-refusal without --force, the --json shape, ls, and the PgDumper argv + Preflight remediation. No real Postgres. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Graduates the spec-15 data-lifecycle verbs for the shared Postgres (thin v2 scope: Postgres-only, per-project tenant):
devstack db snapshot [name]— capture the project's tenant databasedevstack db snapshot ls— list captured snapshots (lock-free)devstack db restore <name>— replay a snapshot into the tenant (destructive; confirm/--yes, refuses non-empty without--force)db reset/db pullremain reserved v2 stubs.How
internal/dbseam: aDumperinterface +PgDumperthat shellspg_dump/pg_restore/psqlbehind an injectableRunner. The release binary stays CGO-free (external tools). The password ridesPGPASSWORDin the process env, never on the argv.Preflightmaps a missing client tool to a one-line remediation, and degrades thedbverbs only (never blocksup).internal/orchestrate/snapshot.goreuses the exact provision host-reachability pattern —engineTarget→FreeHostPort+writeProvisionOverlay+compose up -d <inst>on the shared stack — so the dump/restore tooling reaches the warm Postgres over a ledger-allocated 127.0.0.1 host port without publishing a permanent one. Per spec 15, the dump/restore process runs outside the flock; only the ledger row + event write are locked.$DEVSTACK_HOME/snapshots/<workspace>/<name>.dump(store.SnapshotsPath). Each snapshot is recorded as aprovisioned(kind=snapshot)ledger row plus adb.snapshot/db.restoreevent — thekindcolumn is free-text, so no migration is needed.--force, and honors--project/--db/--instance.--json/--quietcontract throughout.Testing
CGO_ENABLED=0 go build ./...,CGO_ENABLED=1 go test ./internal/...,gofmt -l(clean),go vet ./...— all green. Nointernal/generateortemplates/changes, so determinism is unaffected.New tests (no real Postgres):
pg_dump/pg_restoreargv,PGPASSWORD-in-env (no argv leak), the host-port overlay allocation (ledgerport_allocrow +compose upof the overlay), and the snapshot ledger row.--forceon a non-empty tenant (and success under--force).--jsonshape (documented keys),ls, and thePgDumperargv +Preflightremediation.🤖 Generated with Claude Code