Skip to content
Merged
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
80 changes: 79 additions & 1 deletion go/core/cli/cmd/kagent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import (
"fmt"
"os"
"os/signal"
"strconv"
"syscall"
"time"

cli "github.com/kagent-dev/kagent/go/core/cli/internal/cli/agent"
"github.com/kagent-dev/kagent/go/core/cli/internal/cli/envdoc"
"github.com/kagent-dev/kagent/go/core/cli/internal/cli/mcp"
"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"
dbmigrate "github.com/kagent-dev/kagent/go/core/pkg/cli/db/migrate"
"github.com/kagent-dev/kagent/go/core/pkg/migrations"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand Down Expand Up @@ -449,11 +456,82 @@ 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.NewCommandFromFunc(migrationSources(cfg)))

return rootCmd
}

// vectorEnabledKey names two lookups that deliberately share it: the CLI's
// own DATABASE_VECTOR_ENABLED env var (a local operator override), and the
// controller-configmap key the chart renders — the value the controller pod
// itself consumes via envFrom. Same name, two different places.
const vectorEnabledKey = "DATABASE_VECTOR_ENABLED"

// migrationSources resolves the built-in migration tracks when a db
// subcommand runs (never during command construction, so unrelated commands
// do no work and print no warnings). The vector track is gated, in order of
// precedence, on: the DATABASE_VECTOR_ENABLED env var in the CLI's own
// environment (explicit operator intent, works without a cluster), the
// controller's configmap on the live cluster (the same value the server
// reads), and finally the controller's default (enabled).
func migrationSources(cfg *config.Config) dbmigrate.SourcesFunc {
return func(ctx context.Context) ([]migrations.Source, error) {
vectorEnabled := true
if v := os.Getenv(vectorEnabledKey); v != "" {
b, err := strconv.ParseBool(v)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: invalid %s=%q; assuming true\n", vectorEnabledKey, v)
} else {
vectorEnabled = b
}
} else if b, ok := clusterVectorEnabled(ctx, cfg.Namespace); ok {
vectorEnabled = b
}
return migrations.BuiltinSources(vectorEnabled), nil
}
}

// clusterVectorEnabled reads the vectorEnabledKey entry from the controller
// configmap in the given namespace (the same "kagent-controller" default
// naming the rest of the CLI assumes) — the cluster-side counterpart of the
// env-var override in migrationSources. When the value is used it says so on
// stderr, naming the kubeconfig context it was read from — the lookup follows
// the *current* context, so this is the operator's cue that the cluster and
// their --db-url had better be the same install. Best-effort: reports
// ok=false when no cluster is reachable, the configmap is absent, or the
// value doesn't parse — callers fall back to the default.
func clusterVectorEnabled(ctx context.Context, namespace string) (enabled, ok bool) {
k8sClient, err := cli.CreateKubernetesClient()
if err != nil {
return false, false
}
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
var cm corev1.ConfigMap
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: "kagent-controller"}, &cm); err != nil {
return false, false
}
b, err := strconv.ParseBool(cm.Data[vectorEnabledKey])
if err != nil {
return false, false
}
// Trailing blank line separates the notice from the command's stdout
// when both land on a terminal; piped stdout is unaffected.
fmt.Fprintf(os.Stderr, "resolved vector track from cluster context %q: configmap %s/kagent-controller has %s=%t (set %s to override)\n\n",
currentKubeContext(), namespace, vectorEnabledKey, b, vectorEnabledKey)
return b, true
}

// currentKubeContext names the kubeconfig context the CLI's Kubernetes client
// dials, for operator-facing messages. Best-effort.
func currentKubeContext() string {
raw, err := clientcmd.NewDefaultClientConfigLoadingRules().Load()
if err != nil || raw.CurrentContext == "" {
return "(current kubeconfig context)"
}
return raw.CurrentContext
}

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

Expand Down
54 changes: 54 additions & 0 deletions go/core/pkg/cli/db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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 {
return newCommand(migrate.NewCommand(sources...))
}

// NewCommandFromFunc is NewCommand with deferred source resolution: fn runs
// when a db subcommand executes, not while the command tree is constructed.
// See migrate.NewCommandFromFunc.
func NewCommandFromFunc(fn migrate.SourcesFunc) *cobra.Command {
return newCommand(migrate.NewCommandFromFunc(fn))
}

func newCommand(migrateCmd *cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: "db",
Short: "Database operations (migrations, inspection)",
}
cmd.AddCommand(migrateCmd)

// 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