Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion go/core/cli/cmd/kagent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"os/signal"
"strconv"
"syscall"

cli "github.com/kagent-dev/kagent/go/core/cli/internal/cli/agent"
Expand All @@ -13,6 +14,8 @@ import (
"github.com/kagent-dev/kagent/go/core/cli/internal/config"
"github.com/kagent-dev/kagent/go/core/cli/internal/profiles"
"github.com/kagent-dev/kagent/go/core/cli/internal/tui"
dbcli "github.com/kagent-dev/kagent/go/core/pkg/cli/db"
"github.com/kagent-dev/kagent/go/core/pkg/migrations"
"github.com/spf13/cobra"
"sigs.k8s.io/controller-runtime/pkg/client"
)
Expand Down Expand Up @@ -449,11 +452,28 @@ Examples:
runCmd.Flags().StringVar(&runCfg.ProjectDir, "project-dir", "", "Project directory (default: current directory)")
runCmd.Flags().BoolVar(&runCfg.Build, "build", false, "Rebuild the Docker image before running")

rootCmd.AddCommand(installCmd, uninstallCmd, invokeCmd, bugReportCmd, versionCmd, dashboardCmd, getCmd, initCmd, buildCmd, deployCmd, addMcpCmd, runCmd, mcp.NewMCPCmd(), envdoc.NewEnvCmd())
rootCmd.AddCommand(installCmd, uninstallCmd, invokeCmd, bugReportCmd, versionCmd, dashboardCmd, getCmd, initCmd, buildCmd, deployCmd, addMcpCmd, runCmd, mcp.NewMCPCmd(), envdoc.NewEnvCmd(), dbcli.NewCommand(builtinMigrationSources()...))

return rootCmd
}

// builtinMigrationSources returns the built-in migration tracks for `kagent
// db migrate`. The vector track is gated on the same DATABASE_VECTOR_ENABLED
// env var the controller reads for --database-vector-enabled, with the same
// default (enabled), so the CLI operates on the tracks the server migrates.
func builtinMigrationSources() []migrations.Source {
vectorEnabled := true
if v := os.Getenv("DATABASE_VECTOR_ENABLED"); v != "" {
b, err := strconv.ParseBool(v)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: invalid DATABASE_VECTOR_ENABLED=%q; assuming true\n", v)
} else {
vectorEnabled = b
}
}
return migrations.BuiltinSources(vectorEnabled)
}

func runInteractive(cmd *cobra.Command, args []string, cfg *config.Config) {
client := cfg.Client()

Expand Down
2 changes: 1 addition & 1 deletion go/core/internal/dbtest/dbtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func StartT(ctx context.Context, t *testing.T) string {
// If vectorEnabled is true the vector pass is also applied.
// Use MigrateT in tests that have a *testing.T; use Migrate in TestMain where no T is available.
func Migrate(connStr string, vectorEnabled bool) error {
return migrations.RunUp(connStr, migrations.FS, vectorEnabled)
return migrations.RunUp(context.Background(), connStr, migrations.BuiltinSources(vectorEnabled))
}

// MigrateT runs the embedded migrations against connStr and calls t.Fatal on error.
Expand Down
55 changes: 29 additions & 26 deletions go/core/pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,10 @@ type Config struct {
// that originates TLS upstream. Off by default;
MCPEgressPlaintext bool
Database struct {
Url string
UrlFile string
VectorEnabled bool
Url string
UrlFile string
VectorEnabled bool
SkipMigrations bool
}
Substrate struct {
AteAPIEndpoint string
Expand Down Expand Up @@ -181,6 +182,7 @@ func (cfg *Config) SetFlags(commandLine *flag.FlagSet) {
commandLine.StringVar(&cfg.Database.Url, "postgres-database-url", "postgres://postgres:kagent@kagent-postgresql.kagent.svc.cluster.local:5432/postgres", "The URL of the PostgreSQL database.")
commandLine.StringVar(&cfg.Database.UrlFile, "postgres-database-url-file", "", "Path to a file containing the PostgreSQL database URL. Takes precedence over --postgres-database-url.")
commandLine.BoolVar(&cfg.Database.VectorEnabled, "database-vector-enabled", true, "Enable pgvector extension and memory table. Requires pgvector to be installed on the PostgreSQL server.")
commandLine.BoolVar(&cfg.Database.SkipMigrations, "skip-migrations", false, "Do not run database migrations at startup; instead verify the database is already migrated and fail if it is not. Migrations must be applied out-of-band (e.g. from a pipeline or pre-upgrade hook). Settable via the SKIP_MIGRATIONS env var.")

commandLine.StringVar(&cfg.WatchNamespaces, "watch-namespaces", "", "The namespaces to watch for .")

Expand Down Expand Up @@ -300,17 +302,11 @@ type ExtensionConfig struct {

type GetExtensionConfig func(bootstrap BootstrapConfig) (*ExtensionConfig, error)

// MigrationRunner applies database migrations given the resolved connection URL.
// vectorEnabled mirrors the --database-vector-enabled flag; custom runners can use it
// to conditionally apply vector-specific migrations.
// Returning a non-nil error causes the app to exit.
//
// Pass nil to Start to use the default migration runner (migrations.RunUp with migrations.FS).
// Provide a custom runner to take over the migration process entirely.
// Custom runners that want to include the built-in migrations can call migrations.RunUp directly.
type MigrationRunner func(ctx context.Context, url string, vectorEnabled bool) error

func Start(getExtensionConfig GetExtensionConfig, migrationRunner MigrationRunner) {
// Start boots the controller. extraSources registers additional migration
// tracks beyond the built-in sources; they are applied after the built-in
// (core, vector) tracks, in the order given. Pass nil to run only the built-in
// migrations.
func Start(getExtensionConfig GetExtensionConfig, extraSources []migrations.Source) {
var tlsOpts []func(*tls.Config)
var cfg Config

Expand Down Expand Up @@ -474,20 +470,27 @@ func Start(getExtensionConfig GetExtensionConfig, migrationRunner MigrationRunne
os.Exit(1)
}

// Use the built-in migration runner when none is provided.
if migrationRunner == nil {
migrationRunner = func(_ context.Context, url string, vectorEnabled bool) error {
return migrations.RunUp(url, migrations.FS, vectorEnabled)
}
}

// Run migrations before connecting; schema must exist before queries.
setupLog.Info("running database migrations")
if err := migrationRunner(ctx, dbURL, cfg.Database.VectorEnabled); err != nil {
setupLog.Error(err, "database migration failed")
os.Exit(1)
// Built-in sources run first, then any downstream-registered extras.
// With --skip-migrations (SKIP_MIGRATIONS) the server applies nothing and
// instead verifies the database is already migrated, so migrations can run
// out-of-band and this connection needs no DDL privileges.
sources := append(migrations.BuiltinSources(cfg.Database.VectorEnabled), extraSources...)
if cfg.Database.SkipMigrations {
setupLog.Info("skipping database migrations; verifying schema is migrated")
if err := migrations.VerifyMigrated(ctx, dbURL, sources); err != nil {
setupLog.Error(err, "database migration verification failed")
os.Exit(1)
}
setupLog.Info("database schema verified")
} else {
setupLog.Info("running database migrations")
if err := migrations.RunUp(ctx, dbURL, sources); err != nil {
setupLog.Error(err, "database migration failed")
os.Exit(1)
}
setupLog.Info("database migrations complete")
}
setupLog.Info("database migrations complete")

// Connect to database
db, err := database.Connect(ctx, &database.PostgresConfig{
Expand Down
43 changes: 43 additions & 0 deletions go/core/pkg/cli/db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Package db hosts the `kagent db` parent command and its subcommands.
// Currently only `migrate` is wired; future siblings attach here.
package db

import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/kagent-dev/kagent/go/core/pkg/cli/db/migrate"
"github.com/kagent-dev/kagent/go/core/pkg/migrations"
)

// NewCommand returns the `db` parent command with `migrate` attached. The
// given sources define the migration tracks the subcommands operate on, in
// orchestrator registration order.
func NewCommand(sources ...migrations.Source) *cobra.Command {
cmd := &cobra.Command{
Use: "db",
Short: "Database operations (migrations, inspection)",
}
cmd.AddCommand(migrate.NewCommand(sources...))

// Hide the root's API-oriented persistent flags from help across the
// entire `db` subtree. They target the kagent server / Kubernetes,
// but db commands talk to Postgres directly via --db-url.
//
// Hidden is a property of the flag itself (shared across the whole
// tree), so we can't flip it permanently. The HelpFunc override
// toggles it for the duration of the help render and restores it
// after. Children of `db` that don't set their own HelpFunc walk the
// parent chain and pick this one up.
cmd.SetHelpFunc(func(c *cobra.Command, args []string) {
for _, name := range []string{"config", "kagent-url", "namespace", "output-format", "timeout", "verbose"} {
if f := c.InheritedFlags().Lookup(name); f != nil {
f.Hidden = true
defer func(f *pflag.Flag) { f.Hidden = false }(f)
}
}
c.Root().HelpFunc()(c, args)
})

return cmd
}
Loading
Loading