Skip to content

feat(databases): manage database backups and restores from the CLI - #168

Open
subnetmarco wants to merge 14 commits into
mainfrom
feat/database-backups
Open

feat(databases): manage database backups and restores from the CLI#168
subnetmarco wants to merge 14 commits into
mainfrom
feat/database-backups

Conversation

@subnetmarco

@subnetmarco subnetmarco commented Aug 22, 2026

Copy link
Copy Markdown
Member

The CLI half of database backups. Depends on Kong/volcano-hosting#880, which
adds the routes; the dashboard side is Kong/volcano-web#522.

$ volcano cloud databases backups create app before_migration
$ volcano cloud databases restore app --backup before_migration
$ volcano cloud databases restore app --to 2026-08-20T14:30:00Z
$ volcano cloud databases restores get app 01JB2X...
$ volcano cloud databases backup-schedule set app --frequency daily --hour 3 --retention 168h

Summary

  • backups list|create|get|delete, following the branch commands' shape and
    table output.
  • restore by backup name (--backup) or by timestamp inside the plan's
    window (--to). It queues the restore and reports the id; the database is
    restoring until the platform finishes, and its connection string does not
    change.
  • restores list|get, so the id restore prints is worth having: the
    status, how far it got, and why it stopped if it did. list shows the 50 most
    recent, which is what the endpoint returns.
  • backup-schedule get|set for the provider-side daily, weekly, or monthly
    schedule (--frequency, --hour, --day, --retention, --clear) and its
    retention.
  • Left out of local mode, where there is no provider to back a database up
    with, guarded by a test rather than by convention.
  • Docs say which commands a running restore refuses, since the CLI surfaces the
    API's 409 as-is, and that backups need Pro — the plan refusal reaches the
    reads too, so a 403 on backups list is the plan talking, not the token.

Test plan

  • go test ./...
  • make lint, make openapi-generated-check
  • tests/e2e/api/database_backups_test.go against a deployed environment
    once Kong/volcano-hosting#880 is out.

Made with Cursor

subnetmarco and others added 2 commits August 22, 2026 08:04
Adds `backups list|create|get|delete`, `restore` by backup name or timestamp,
and `backup-schedule get|set` under the cloud tree. The commands are left out
of local mode, where there is no provider to back a database up with.

Co-authored-by: Cursor <cursoragent@cursor.com>
The CLI surfaces the API's 409 as-is, so the commands that wait for a restore
were only discoverable by hitting one.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI balanced review requested due to automatic review settings August 22, 2026 18:26
@subnetmarco
subnetmarco requested a review from a team as a code owner August 22, 2026 18:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

subnetmarco and others added 10 commits August 22, 2026 12:35
Backups, restores, and branches need the storage provider, which local
development does not run, so the local command tree leaves them out. Cobra
answers a subcommand a tree does not have by printing the parent's help and
exiting 0, so `volcano databases backups list app` looked like it worked and
said nothing about the command living under `volcano cloud`. Hidden stubs
answer those names with the cloud path instead, asserted in the command tests
and against the running stack in the local-mode E2E.

Sync the vendored spec with the platform's four new backup `409`s, pass them to
the error mapper so a restore-in-flight or still-provisioning database keeps its
message, and cover the paths that had none: a duplicate create, a missing
backup, a delete refused by a running restore, a monthly schedule, and an empty
`--to`. Document `--yes`, the per-frequency `--day`, and how to run the cloud
API E2E, which no CI job does.

Co-authored-by: Cursor <cursoragent@cursor.com>
Starting a restore printed an id and then pointed at `databases get`, which
only ever reports that the database is restoring. When a restore failed the
reason was on the restore record, reachable from the API and the dashboard
but from nothing in the CLI, so a failed restore read as a database that had
gone `failed` for no stated reason.

Adds `databases restores list` and `databases restores get`, which the
restore command now names with the id it just printed, and which say what
the status means for the database rather than leaving it to be inferred.

The local tree turns both away with the cloud path, like the rest of the
provider-backed commands, and the local-mode smoke test covers the aliases
and `restore --to` it was missing.

Co-authored-by: Cursor <cursoragent@cursor.com>
Backups are a paid capability now, and the plan refusal reaches the reads
too, so a 403 on `backups list` is the expected answer rather than a
token problem. Say so up front and cover it in the command tests, which
is what keeps the CLI passing the reason through instead of the status.

Co-authored-by: Cursor <cursoragent@cursor.com>
Backup storage counts against the parent database's storage allowance, and the
Size column is what a backup holds rather than what it costs to keep, so a
schedule's backups read larger than they are billed.

Co-authored-by: Cursor <cursoragent@cursor.com>
The stats endpoint now reports `backup_storage_bytes` and folds it into
`current_storage_bytes`, and a database's `storage_bytes` gauge carries it too.

Co-authored-by: Cursor <cursoragent@cursor.com>
The list's storage_bytes is written by a background pass, so it can trail the
stats endpoint's current_storage_bytes by more than the few minutes the backup
figure's own wording promises.

Co-authored-by: Cursor <cursoragent@cursor.com>
…s answer

Co-authored-by: Cursor <cursoragent@cursor.com>
The API numbers the week from Monday: 1 to 7, with no zero. --day
accepted 0 to 6, so every weekday named the one before it, Sunday could
not be asked for, and a weekly schedule with the flag left off sent a day
the API refuses. It is required for a weekly schedule now, and the
schedule the CLI prints back names the day the API meant.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ers to

The guard is what turns an unknown local subcommand from cobra's
help-and-exit-0 into an error, so a spelling it does not answer to reads as
success. Nothing covered `branch` or `restore-history`, and the local-mode
E2E never invoked branches at all despite checking that local help hides
them. The guard also has to name a command the cloud tree answers to, which
is now asserted rather than assumed.

Co-authored-by: Cursor <cursoragent@cursor.com>
A branch shares its parent's data, not its credentials. The cloud E2E
rotated a branch's password and only checked the command reported success,
so a rotation that reached the parent's connection string would have gone
unnoticed.

Co-authored-by: Cursor <cursoragent@cursor.com>
tkkhq
tkkhq previously requested changes Aug 23, 2026

@tkkhq tkkhq left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI-specific findings are attached inline.

There is also a cross-repository compatibility concern in Kong/volcano-hosting#880: its public docs guarantee that a backup schedule survives restore, but carryBackupSchedule and moveSchedule swallow provider read/write failures and let repair continue (https://github.com/Kong/volcano-hosting/blob/3f861693feeac1149420294f5fc1b54c6e11e14c/internal/databasebackups/repair.go#L89-L130). A transient failure can therefore complete the restore with no schedule on the restored branch, while the orphan may keep producing billed snapshots.

Otherwise, the five route shapes, request bodies, response structures, status codes, retention units, and weekday numbering are compatible across CLI #168, Hosting #880, and Web #522. I verified CLI head 905cf97e with go test ./..., lint, and generated-client checks; focused Hosting backup/restore tests passed at 3f861693.

Comment thread internal/output/database_backup.go Outdated
Comment thread internal/cmd/databases/backup/restores.go Outdated
Comment thread internal/cmd/databases/backup/list.go
@tkkhq
tkkhq dismissed their stale review August 23, 2026 21:11

Non-blocking review.

tkkhq

This comment was marked as duplicate.

subnetmarco and others added 2 commits August 23, 2026 14:44
An exhausted restore does not always leave the database failed. A backup that no
longer exists at the provider ends the restore before anything is replaced, and
the database goes back to active untouched — so telling the user their data "is
whatever the last attempt left behind" was wrong in exactly the case that is
most alarming and least deserved.

The note now says Volcano gave up and what each outcome means, and the vendored
contract and the docs carry the same wording as the API.

Also says the restore list is the 50 most recent rather than every restore,
which is what the endpoint returns.

Co-authored-by: Cursor <cursoragent@cursor.com>
Volcano retries a restore attempt on its own and the restore reads
`pending` in between, so `failed` and `exhausted` are the same news: it is
done trying. The note told the reader another attempt was coming, which
left them waiting on nothing.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

3 participants