Repository navigation
Conversation
# Conflicts: # cmd/volcano/grant_test.go
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved findings include potential SQL or private-data leakage into telemetry and misleading retry guidance that can lead callers to start another mutation rather than resume polling.
4 open issues
What changed in this PR
This PR enables database migrations under delegated grants through server-side migration runs, extending the grant support added by the preceding authentication and invocation changes. Other credentials retain the existing direct migration path.
Changes:
- Route grant-based
migration upanddeploy --with-migrationsthrough tracked runs, with polling, outcome reporting, and sensitive-error handling. - Add an explicit
migration baselinecommand with confirmation and JSON output. - Add API integration and command-tree tests, plus documentation for grant-based migrations.
| File | Description |
|---|---|
internal/cmd/deploy/grant_test.go |
Tests deploy routes, migration runs, and grant-budget refusals. |
internal/cmd/databases/migration/runs.go |
Starts, follows, and reports server-side migration runs. |
internal/cmd/databases/migration/runs_test.go |
Tests grant migration requests, outcomes, and failures. |
internal/cmd/databases/migration/migration.go |
Registers baseline and routes grant migrations to runs. |
internal/cmd/databases/migration/baseline.go |
Implements file selection, confirmation, and baseline reporting. |
internal/cmd/databases/migration/baseline_test.go |
Tests baseline selection, output, and refusals. |
internal/api/database_migration_runs.go |
Adds run creation, polling, baseline, and conflict handling. |
docs/databases.md |
Explains server-side migrations under delegated grants. |
cmd/volcano/grant_test.go |
Tests migration commands through the real command tree. |
Review effort: Balanced
swkeever
added a commit
that referenced
this pull request
Nov 13, 2026
Rename internal/api/database_migrations.go to database_migration_runs.go and the `migration runs` commands from runs.go to runs_list.go and runs_get.go, and name the in-progress code MigrationRunInProgressCode, so #478 can rebase onto this branch without both PRs adding runs.go. Run reads and a refused run start keep only an error body's own fields, since a run's file errors can quote data.
swkeever
added a commit
that referenced
this pull request
Nov 13, 2026
* feat(databases): apply migrations in tracked runs Add `databases migration status`, `apply`, `baseline`, and `runs list|get` over the hosting migration-history and migration-run API. `deploy --with-migrations` now starts a tracked run per database instead of running every file through the database proxy, and reads `volcano/migrations` with the Git deploy layout: files at the top level for one database, or one directory per database. Tracked runs need the `databases.migrations` capability. Where the API does not offer it, apply, baseline, and --with-migrations refuse before anything changes and name the untracked `databases migration up --all`, which stays. * fix(databases): address review of tracked migration runs - Refuse a migration whose name doesn't end in lowercase .sql before a tracked run or baseline sends it, naming the file to rename. Untracked `up` still runs such files. - Send the baseline prompt to stderr with --json, so stdout stays JSON. - Stop claiming a backup was taken when the run ended in its backup phase. - Name the database's directory when a per-database deploy finds it empty, and in `up`'s "not found" error. * refactor(databases): align migration run files with the grant path Rename internal/api/database_migrations.go to database_migration_runs.go and the `migration runs` commands from runs.go to runs_list.go and runs_get.go, and name the in-progress code MigrationRunInProgressCode, so #478 can rebase onto this branch without both PRs adding runs.go. Run reads and a refused run start keep only an error body's own fields, since a run's file errors can quote data. * docs(databases): document the no-transaction migration marker Tracked runs accept a file whose first line is `-- volcano:no-transaction`, which runs alone outside a transaction and again from its start on a retry. docs/databases.md and `migration apply --help` said such statements could not run at all.
Collaborator
|
Overlap note: #365 merged (b46711d), so this PR rebases onto What
What this PR still has to fold in:
|
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.


Tracking
databases.migrate; the C-series adds the baseline route as an audited mutation), Kong/volcano-hosting#3313 (C3, a run start or baseline spends one mutation), Kong/volcano-hosting#3311 (C1)databases migration apply/status/baseline/runsfor every credential; overlaps this PR, see below)docs/design/builder-app/designs/grant-acceptance.md§7 K3 in Kong/volcano-builderWhy
volcano cloud databases migration upreads the database'sconnection_string(GET /projects/{id}/databases/{name}) and connects to Postgres from the client. A grant can't do that: the connection string is a credential that outlives the run, and Hosting keeps that route off the grant table. In local mode the direct path also used a fixed port and could write into another stack's database.database_migrationsgate, and admits a grant on those routes.Summary
Kept to what Hosting requires ("the CLI carries minimal changes; things are controlled server-side"). Hosting decides, the CLI sends and reports.
internal/api/database_migration_runs.go:CreateDatabaseMigrationRun(sends anIdempotency-Key; a 200 replay counts like a 202),GetDatabaseMigrationRun, andBaselineDatabaseMigrations. A 409 keeps the API's code and names the run in progressinternal/cmd/databases/migration/runs.go.runUpbranches to it under apg-grant before it would read the database, soup --all,up -f <file>anddeploy --with-migrations(throughApplyAll) all use a run. Other credentials keep today's direct path002_second.sql: relation "second" already exists (line 1, SQLSTATE 42P07)), so the caller can decide what to baseline. That part is marked sensitive: it can quote SQL and data, so usage telemetry replaces it with[redacted]databases migration baseline -d <db> (--all | -f <file>...) [--yes] [--json], which records the named files throughPOST …/databases/{name}/migrations/baselineand never reads the connection string. Baselining is explicit: the caller names the files, and the CLI detects nothing. Flags match the baseline command in feat(databases): apply migrations in tracked runs #365, so the two merge. It prompts unless--yes, and withVOLCANO_NONINTERACTIVEit refuses without--yes, before any requestmigration_run_in_progressnames it; the caller runs again later), file-name rules (400), the gate being off (404feature_unavailable; the CLI never falls back to the direct path), and every grant refusal and budgetdatabases migration apply) oncedatabase_migrationsis on in production. Whichever of feat(databases): apply migrations in tracked runs #365 and this PR lands second takes in the other; the overlap is one branch inrunUp, the baseline command, and the new filesupwas dropped (Ted). Nothing a grant can call says which files a database already holds, and a whole-database guess would also baseline a new file. The caller reads the failed run's error and baselines the files it namesdocs/databases.md, and one inup --help.sqlname check, and a SQLSTATE-based baseline hintVerification
go build ./...make check(openapi-generated-check,release-workflow-check,lintwith 0 issues,test)internal/cmd/databases/migration/runs_test.gorunsupwith the grant against a recording migration-runs API and an executor that fails if called. It covers the happy path (POST then reads, request body, idempotency key),-fsending one file, a 200 replay that already ended, a failed run whose error names the object and is marked sensitive,feature_unavailable, a 409 naming the run in progress, a 401 while polling, a spent budget keeping exit6, andApplyAll. Each asserts there is noGET …/databases/appand that the grant is never printedinternal/cmd/databases/migration/baseline_test.go: named files and--allgo to the baseline route with their SQL;--json; refused without--yeswhen no one can answer;checksum_mismatch,grant_budget_exhausted,grant_route_not_allowedandfeature_unavailablekeep their status and code; an unknown file is refused before any requestTestGrantMigrationUpUsesAMigrationRunandTestGrantBaselineUsesTheBaselineRouteincmd/volcanorun through the real command tree with the worker's environment (baseline: exit0,5when refused,6on a spent budget)internal/cmd/deploy/grant_test.goruns the whole-projectdeployunder a grant against a copy of Hosting's route table (DefaultGrantRoutes()at2f42b4c314ontkkhq/grant-acceptance-invoke, Kong/volcano-hosting#3329, the top of the grant chain), with each route's kind and the config dry-run-as-read rule. Every request, the source-mode read included, is on the table and carries the grant.--with-migrationsuses a run and never reads the database, and a 429grant_budget_exhaustedon each mutation keeps exit6volcano cloud deployfirst readsGET /projects/{id}/source-export. Hosting C2 lists it asproject.read, with no CLI change. The copied table ininternal/cmd/deploy/grant_test.gonow has it, andTestDeployUnderAGrantIsRefusedTheSourceModeReadis removedPUT …/config?dry_run=truecounts as a read in Hosting C3, with no CLI changemake localmode-e2eand the cloud E2E suites (Docker and staging are out of scope here)Decisions for Ted
baselineasks for confirmation unless--yes, as feat(databases): apply migrations in tracked runs #365's does; a worker passes--yes. Dropping the prompt would be smaller, but feat(databases): apply migrations in tracked runs #365 would then differ, and a recorded file never runs