Skip to content

Add kagent db migrate CLI for out-of-band migrations#2168

Merged
EItanya merged 12 commits into
mainfrom
iplay88keys/migrations-improvements-pt-4
Jul 9, 2026
Merged

Add kagent db migrate CLI for out-of-band migrations#2168
EItanya merged 12 commits into
mainfrom
iplay88keys/migrations-improvements-pt-4

Conversation

@iplay88keys

@iplay88keys iplay88keys commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a kagent db migrate command group for running and inspecting database migrations out-of-band from server startup.

kagent db migrate up                    # apply all pending migrations, every registered source
kagent db migrate status [--output json] # aggregate + per-source applied/pending breakdown
kagent db migrate version               # per-source current versions (--source filters)
kagent db migrate down N  --source core # roll back the N most-recent migrations on one source
kagent db migrate goto V  --source core # move one source to version V (0 = empty schema)
kagent db migrate force V --source core # assert a version without running SQL (recovery)
  • New exported packages go/core/pkg/cli/db and go/core/pkg/cli/db/migrate; NewCommand(sources ...migrations.Source) takes the source list explicitly so downstream consumers can wire their own sources.
  • The connection comes from --db-url, falling back to the controller's POSTGRES_DATABASE_URL env var. The vector track is gated on the controller's DATABASE_VECTOR_ENABLED env var (default enabled).
  • New migrations.WithMigrator export: the CLI opens migrators through the orchestrator's own schema handling, tracking-table config, and advisory-lock identity, so a CLI invocation racing a booting server serializes.
  • up, down, and goto refuse a dirty source and report the force invocation that clears it; the CLI never auto-recovers (startup remains the automatic tier). status/version report dirty state instead of refusing.
  • force 0 clears the version record entirely (recovery when a track's first migration fails dirty); any other value must be a shipped migration version.
  • down on an empty track reports "no migrations to roll back" instead of golang-migrate's raw "file does not exist".
  • status --output json has a frozen field shape locked by TestStatusJSONShape.
  • Help output for the db subtree hides the root's server-oriented persistent flags (--kagent-url, -n, -o, --timeout, etc.); db commands talk to Postgres directly.

Testing

Version:

❯ ./go/core/bin/kagent-local db migrate version
core: 6
vector: 3

Fresh install:

❯ ./go/core/bin/kagent-local db migrate status
9 migration(s) applied, 0 pending
  core: 6 applied (at v6), 0 pending
  vector: 3 applied (at v3), 0 pending

kagent=# \dt
                   List of tables
 Schema |           Name           | Type  | Owner
--------+--------------------------+-------+--------
⋮
(17 rows)

Down (Rollback):

kagent=# \dt
                   List of tables
 Schema |           Name           | Type  | Owner
--------+--------------------------+-------+--------
⋮
(17 rows)

❯ ./go/core/bin/kagent-local db migrate down 1 --source core 
rolled back 1 migration(s)

❯ ./go/core/bin/kagent-local db migrate status              
8 migration(s) applied, 1 pending
  core: 5 applied (at v5), 1 pending
  vector: 3 applied (at v3), 0 pending

kagent=# \dt
                   List of tables
 Schema |           Name           | Type  | Owner
--------+--------------------------+-------+--------
⋮
(15 rows)

Goto:

❯ ./go/core/bin/kagent-local db migrate goto 1 --source core
schema is at version 1

kagent=# \dt
                   List of tables
 Schema |           Name           | Type  | Owner
--------+--------------------------+-------+--------
 public | memory                   | table | kagent
 public | schema_migrations        | table | kagent
 public | vector_schema_migrations | table | kagent
(3 rows)

Force:

kagent=# select * from schema_migrations;
 version | dirty
---------+-------
       6 | f
(1 row)

❯ ./go/core/bin/kagent-local db migrate force 1 --source core 
version 1 marked as applied

kagent=# select * from schema_migrations;
 version | dirty
---------+-------
       1 | f
(1 row)

Goto + Up:

❯ ./go/core/bin/kagent-local db migrate goto 1 --source core
schema is at version 1

❯ ./go/core/bin/kagent-local db migrate goto 1 --source vector
schema is at version 1

❯ ./go/core/bin/kagent-local db migrate status                
2 migration(s) applied, 7 pending
  core: 1 applied (at v1), 5 pending
  vector: 1 applied (at v1), 2 pending

❯ ./go/core/bin/kagent-local db migrate up                    
applied 7 migration(s); schema is up to date

iplay88keys and others added 7 commits June 26, 2026 08:22
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
…s-improvements-pt-3

Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
@iplay88keys
iplay88keys marked this pull request as ready for review July 6, 2026 15:29
@iplay88keys
iplay88keys changed the base branch from iplay88keys/migrations-improvements-pt-3 to main July 6, 2026 15:29
@iplay88keys
iplay88keys requested a review from EItanya July 6, 2026 15:42
@iplay88keys iplay88keys changed the title add kagent db migrate CLI for out-of-band migrations Add kagent db migrate CLI for out-of-band migrations Jul 6, 2026
@iplay88keys
iplay88keys changed the base branch from main to iplay88keys/migrations-improvements-pt-3 July 6, 2026 16:18
@EItanya
EItanya requested review from ilackarms and yuval-k as code owners July 6, 2026 18:25
@iplay88keys
iplay88keys marked this pull request as draft July 6, 2026 19:57
@iplay88keys
iplay88keys changed the base branch from iplay88keys/migrations-improvements-pt-3 to main July 6, 2026 19:57
@iplay88keys
iplay88keys force-pushed the iplay88keys/migrations-improvements-pt-4 branch from d104f2f to 03bf283 Compare July 6, 2026 19:57
…s-improvements-pt-4

Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
@iplay88keys
iplay88keys marked this pull request as ready for review July 8, 2026 14:21
@iplay88keys
iplay88keys requested a review from a team as a code owner July 8, 2026 14:21
Copilot AI review requested due to automatic review settings July 8, 2026 14:21

Copilot AI 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.

Pull request overview

Adds an out-of-band database migration CLI (kagent db migrate) that uses the same migration orchestration and locking behavior as server startup, enabling operators to apply/inspect/rollback migrations without booting the controller.

Changes:

  • Export migrations.WithMigrator to open/close a migrator with the orchestrator’s schema/tracking-table/lock configuration.
  • Introduce go/core/pkg/cli/db and go/core/pkg/cli/db/migrate implementing up/down/goto/force/status/version, including JSON status output and dirty-state refusal semantics.
  • Wire the db command subtree into the main kagent CLI and add unit + container-backed tests for the new migrate commands.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
go/core/pkg/migrations/runner.go Adds exported WithMigrator helper to share orchestrator migrator setup with CLI tooling.
go/core/pkg/cli/db/migrate/migrate.go Implements the kagent db migrate command group and subcommands, output, and validation logic.
go/core/pkg/cli/db/migrate/migrate_test.go Adds unit + Postgres container-backed tests covering the CLI behavior and JSON shape.
go/core/pkg/cli/db/db.go Adds kagent db parent command and hides root persistent flags in help output for the subtree.
go/core/cli/cmd/kagent/main.go Wires the new db command into the root CLI and selects built-in migration sources.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread go/core/pkg/cli/db/migrate/migrate.go Outdated
Comment thread go/core/cli/cmd/kagent/main.go Outdated
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
…s-improvements-pt-4

Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Comment thread go/core/cli/cmd/kagent/main.go Outdated
func newDBCommand() *cobra.Command {
vectorEnabled := true
var envWarning string
if v := os.Getenv("DATABASE_VECTOR_ENABLED"); v != "" {

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.

Can we somehow read this from the live cluster rather than requiring information from the user. We can use this as a fallback of course

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good question, I will look into that.

@iplay88keys iplay88keys Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have a way to look up the kagent's config. The only issue is that if the kubeconfig is pointing to a Kagent which is attached to a different postgres than you are connecting to via --db-url, the looked-up value of VECTOR_ENABLED could silently differ from what's in postgres. I can't detect a mismatch, but I can print it in the cli output as stderr:

❯ go run ./core/cli/cmd/kagent db migrate status   
resolved vector track from cluster context "kind-cluster": configmap kagent/kagent-controller has DATABASE_VECTOR_ENABLED=false (set DATABASE_VECTOR_ENABLED to override)

9 migration(s) applied, 0 pending
  core: 6 applied (at v6), 0 pending
  vector: 3 applied (at v3), 0 pending

…rate CLI

Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Comment thread go/core/cli/cmd/kagent/main.go Outdated
func migrationSources(cfg *config.Config) dbmigrate.SourcesFunc {
return func(ctx context.Context) ([]migrations.Source, error) {
vectorEnabled := true
if v := os.Getenv("DATABASE_VECTOR_ENABLED"); v != "" {

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.

Why do you have this and the check inside of clusterVectorEnabled

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

They are technically different sources (env var override for the cli vs the deployed controller's configmap key). The env var wins when both are set. I've made that more clear in the code.

Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
@EItanya
EItanya merged commit 8c82d84 into main Jul 9, 2026
32 checks passed
@EItanya
EItanya deleted the iplay88keys/migrations-improvements-pt-4 branch July 9, 2026 14:36
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