Skip to content

feat(db): snapshot and restore the shared Postgres (spec 15) - #100

Merged
gustavobertoi merged 1 commit into
mainfrom
feat/db-snapshot-restore
Jul 1, 2026
Merged

feat(db): snapshot and restore the shared Postgres (spec 15)#100
gustavobertoi merged 1 commit into
mainfrom
feat/db-snapshot-restore

Conversation

@gustavobertoi

Copy link
Copy Markdown
Contributor

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 database
  • devstack 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 pull remain reserved v2 stubs.

How

  • New internal/db seam: a Dumper interface + PgDumper that shells pg_dump/pg_restore/psql behind an injectable Runner. The release binary stays CGO-free (external tools). The password rides PGPASSWORD in the process env, never on the argv. Preflight maps a missing client tool to a one-line remediation, and degrades the db verbs only (never blocks up).
  • internal/orchestrate/snapshot.go reuses the exact provision host-reachability pattern — engineTargetFreeHostPort + 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.
  • Dumps + a JSON sidecar land under $DEVSTACK_HOME/snapshots/<workspace>/<name>.dump (store.SnapshotsPath). Each snapshot is recorded as a provisioned(kind=snapshot) ledger row plus a db.snapshot/db.restore event — the kind column is free-text, so no migration is needed.
  • Restore re-hashes the dump for integrity, refuses a non-empty tenant without --force, and honors --project / --db / --instance. --json/--quiet contract throughout.

Testing

CGO_ENABLED=0 go build ./..., CGO_ENABLED=1 go test ./internal/..., gofmt -l (clean), go vet ./... — all green. No internal/generate or templates/ changes, so determinism is unaffected.

New tests (no real Postgres):

  • snapshot → restore round-trip via a mock runner — asserts pg_dump/pg_restore argv, PGPASSWORD-in-env (no argv leak), the host-port overlay allocation (ledger port_alloc row + compose up of the overlay), and the snapshot ledger row.
  • restore refusal without --force on a non-empty tenant (and success under --force).
  • non-TTY --json shape (documented keys), ls, and the PgDumper argv + Preflight remediation.

🤖 Generated with Claude Code

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>
@gustavobertoi
gustavobertoi merged commit b7419b6 into main Jul 1, 2026
4 checks passed
@gustavobertoi
gustavobertoi deleted the feat/db-snapshot-restore branch July 1, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant