From 94c7ccd4ae16d0ed82521ffc132ff4087271a3aa Mon Sep 17 00:00:00 2001 From: Akash Kumar Date: Sat, 25 Apr 2026 03:44:35 +0530 Subject: [PATCH] Honor kagent CLI config defaults Signed-off-by: Akash Kumar --- go/core/cli/cmd/kagent/main.go | 60 ++++++++-------- go/core/cli/cmd/kagent/main_test.go | 104 ++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 27 deletions(-) create mode 100644 go/core/cli/cmd/kagent/main_test.go diff --git a/go/core/cli/cmd/kagent/main.go b/go/core/cli/cmd/kagent/main.go index 8792835b0..78b6c062e 100644 --- a/go/core/cli/cmd/kagent/main.go +++ b/go/core/cli/cmd/kagent/main.go @@ -6,7 +6,6 @@ import ( "os" "os/signal" "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" @@ -34,20 +33,43 @@ func main() { cancel() }() - cfg := &config.Config{} + cfg, err := loadConfig() + if err != nil { + fmt.Fprintf(os.Stderr, "Error initializing config: %v\n", err) + os.Exit(1) + } + + rootCmd := newRootCommand(ctx, cfg) + if err := rootCmd.ExecuteContext(ctx); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + + os.Exit(1) + } +} + +func loadConfig() (*config.Config, error) { + if err := config.Init(); err != nil { + return nil, err + } + return config.Get() +} +func newRootCommand(ctx context.Context, cfg *config.Config) *cobra.Command { rootCmd := &cobra.Command{ Use: "kagent", Short: "kagent is a CLI and TUI for kagent", Long: "kagent is a CLI and TUI for kagent", - Run: runInteractive, + Run: func(cmd *cobra.Command, args []string) { + runInteractive(cmd, args, cfg) + }, } + rootCmd.SetContext(ctx) - rootCmd.PersistentFlags().StringVar(&cfg.KAgentURL, "kagent-url", "http://localhost:8083", "KAgent URL") - rootCmd.PersistentFlags().StringVarP(&cfg.Namespace, "namespace", "n", "kagent", "Namespace") - rootCmd.PersistentFlags().StringVarP(&cfg.OutputFormat, "output-format", "o", "table", "Output format") - rootCmd.PersistentFlags().BoolVarP(&cfg.Verbose, "verbose", "v", false, "Verbose output") - rootCmd.PersistentFlags().DurationVar(&cfg.Timeout, "timeout", 300*time.Second, "Timeout") + rootCmd.PersistentFlags().StringVar(&cfg.KAgentURL, "kagent-url", cfg.KAgentURL, "KAgent URL") + rootCmd.PersistentFlags().StringVarP(&cfg.Namespace, "namespace", "n", cfg.Namespace, "Namespace") + rootCmd.PersistentFlags().StringVarP(&cfg.OutputFormat, "output-format", "o", cfg.OutputFormat, "Output format") + rootCmd.PersistentFlags().BoolVarP(&cfg.Verbose, "verbose", "v", cfg.Verbose, "Verbose output") + rootCmd.PersistentFlags().DurationVar(&cfg.Timeout, "timeout", cfg.Timeout, "Timeout") installCfg := &cli.InstallCfg{ Config: cfg, } @@ -348,7 +370,7 @@ Examples: // Add flags for deploy command deployCmd.Flags().StringVarP(&deployCfg.Image, "image", "i", "", "Image to use (defaults to localhost:5001/{agentName}:latest)") deployCmd.Flags().StringVar(&deployCfg.EnvFile, "env-file", "", "Path to .env file containing environment variables (including API keys)") - deployCmd.Flags().StringVar(&deployCfg.Config.Namespace, "namespace", "kagent", "Kubernetes namespace to deploy to") + deployCmd.Flags().StringVar(&deployCfg.Config.Namespace, "namespace", cfg.Namespace, "Kubernetes namespace to deploy to") deployCmd.Flags().BoolVar(&deployCfg.DryRun, "dry-run", false, "Output YAML manifests without applying them to the cluster") deployCmd.Flags().StringVar(&deployCfg.Platform, "platform", "", "Target platform for Docker build (e.g., linux/amd64, linux/arm64)") @@ -429,26 +451,10 @@ Examples: rootCmd.AddCommand(installCmd, uninstallCmd, invokeCmd, bugReportCmd, versionCmd, dashboardCmd, getCmd, initCmd, buildCmd, deployCmd, addMcpCmd, runCmd, mcp.NewMCPCmd(), envdoc.NewEnvCmd()) - // Initialize config - if err := config.Init(); err != nil { - fmt.Fprintf(os.Stderr, "Error initializing config: %v\n", err) - os.Exit(1) - } - - if err := rootCmd.ExecuteContext(ctx); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - - os.Exit(1) - } + return rootCmd } -func runInteractive(cmd *cobra.Command, args []string) { - cfg, err := config.Get() - if err != nil { - fmt.Fprintf(os.Stderr, "Error getting config: %v\n", err) - os.Exit(1) - } - +func runInteractive(cmd *cobra.Command, args []string, cfg *config.Config) { client := cfg.Client() // Start port forward and ensure it is healthy. diff --git a/go/core/cli/cmd/kagent/main_test.go b/go/core/cli/cmd/kagent/main_test.go new file mode 100644 index 000000000..f0bc87e3a --- /dev/null +++ b/go/core/cli/cmd/kagent/main_test.go @@ -0,0 +1,104 @@ +package main + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + "github.com/kagent-dev/kagent/go/core/cli/internal/config" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoadConfigReadsConfigFileValues(t *testing.T) { + resetConfigState(t) + + homeDir := t.TempDir() + t.Setenv("HOME", homeDir) + + configDir := filepath.Join(homeDir, ".kagent") + require.NoError(t, os.MkdirAll(configDir, 0755)) + require.NoError(t, os.WriteFile(filepath.Join(configDir, "config.yaml"), []byte(` +kagent_url: http://kagent.example.test +namespace: configured-ns +output_format: json +verbose: true +timeout: 45s +`), 0600)) + + cfg, err := loadConfig() + require.NoError(t, err) + + assert.Equal(t, "http://kagent.example.test", cfg.KAgentURL) + assert.Equal(t, "configured-ns", cfg.Namespace) + assert.Equal(t, "json", cfg.OutputFormat) + assert.True(t, cfg.Verbose) + assert.Equal(t, 45*time.Second, cfg.Timeout) +} + +func TestRootCommandUsesConfigValuesAsFlagDefaults(t *testing.T) { + cfg := &config.Config{ + KAgentURL: "http://kagent.example.test", + Namespace: "configured-ns", + OutputFormat: "json", + Verbose: true, + Timeout: 45 * time.Second, + } + + rootCmd := newRootCommand(context.Background(), cfg) + + assert.Equal(t, "http://kagent.example.test", rootCmd.PersistentFlags().Lookup("kagent-url").DefValue) + assert.Equal(t, "configured-ns", rootCmd.PersistentFlags().Lookup("namespace").DefValue) + assert.Equal(t, "json", rootCmd.PersistentFlags().Lookup("output-format").DefValue) + assert.Equal(t, "true", rootCmd.PersistentFlags().Lookup("verbose").DefValue) + assert.Equal(t, "45s", rootCmd.PersistentFlags().Lookup("timeout").DefValue) + + deployCmd, _, err := rootCmd.Find([]string{"deploy"}) + require.NoError(t, err) + require.NotNil(t, deployCmd) + + assert.Equal(t, "configured-ns", deployCmd.Flags().Lookup("namespace").DefValue) + assert.Equal(t, "configured-ns", cfg.Namespace) +} + +func TestRootCommandFlagsOverrideConfigValues(t *testing.T) { + cfg := &config.Config{ + KAgentURL: "http://kagent.example.test", + Namespace: "configured-ns", + OutputFormat: "json", + Verbose: false, + Timeout: 45 * time.Second, + } + + rootCmd := newRootCommand(context.Background(), cfg) + require.NoError(t, rootCmd.ParseFlags([]string{ + "--kagent-url", "http://flag.example.test", + "--namespace", "flag-ns", + "--output-format", "yaml", + "--verbose", + "--timeout", "10s", + })) + + assert.Equal(t, "http://flag.example.test", cfg.KAgentURL) + assert.Equal(t, "flag-ns", cfg.Namespace) + assert.Equal(t, "yaml", cfg.OutputFormat) + assert.True(t, cfg.Verbose) + assert.Equal(t, 10*time.Second, cfg.Timeout) +} + +func resetConfigState(t *testing.T) { + t.Helper() + + oldCommandLine := pflag.CommandLine + viper.Reset() + pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError) + + t.Cleanup(func() { + viper.Reset() + pflag.CommandLine = oldCommandLine + }) +}