diff --git a/cmd/virtwork/main.go b/cmd/virtwork/main.go index df3146b..f17808c 100644 --- a/cmd/virtwork/main.go +++ b/cmd/virtwork/main.go @@ -51,14 +51,7 @@ realistic CPU, memory, database, network, and disk I/O metrics.`, SilenceUsage: true, } - pf := rootCmd.PersistentFlags() - pf.String("namespace", "", "Kubernetes namespace for VMs") - pf.String("kubeconfig", "", "Path to kubeconfig file") - pf.String("config", "", "Path to YAML config file") - pf.Bool("verbose", false, "Enable verbose output") - pf.Bool("audit", true, "Enable audit logging to SQLite") - pf.Bool("no-audit", false, "Disable audit logging") - pf.String("audit-db", "", "Path to audit database file") + config.BindPersistentFlags(rootCmd) rootCmd.AddCommand(newRunCmd(), newCleanupCmd()) return rootCmd @@ -74,20 +67,7 @@ via systemd.`, RunE: runE, } - f := cmd.Flags() - f.StringSlice("workloads", workloads.AllWorkloadNames(), "Workloads to deploy (comma-separated)") - f.Int("vm-count", 1, "Number of VMs per workload") - f.Int("cpu-cores", 0, "CPU cores per VM") - f.String("memory", "", "Memory per VM (e.g., 2Gi)") - f.String("disk-size", "", "Data disk size") - f.String("container-disk-image", "", "Container disk image for VMs") - f.Bool("dry-run", false, "Print specs without creating resources") - f.Bool("no-wait", false, "Skip waiting for VM readiness") - f.Int("timeout", 0, "Readiness timeout in seconds") - f.String("ssh-user", "", "SSH user for VMs") - f.String("ssh-password", "", "SSH password for VMs") - f.StringSlice("ssh-key", nil, "SSH authorized key (repeatable)") - f.StringSlice("ssh-key-file", nil, "SSH key file path (repeatable)") + config.BindRunFlags(cmd, workloads.AllWorkloadNames()) return cmd } @@ -100,10 +80,7 @@ func newCleanupCmd() *cobra.Command { RunE: cleanupE, } - cmd.Flags().Bool("delete-namespace", false, "Also delete the namespace") - cmd.Flags().String("run-id", "", "Only delete resources from this specific run (UUID)") - cmd.Flags().Bool("dry-run", false, "Print intent without destroying resources") - cmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and proceed with cleanup") + config.BindCleanupFlags(cmd) return cmd } diff --git a/cmd/virtwork/main_test.go b/cmd/virtwork/main_test.go index dba2ccd..a66c52f 100644 --- a/cmd/virtwork/main_test.go +++ b/cmd/virtwork/main_test.go @@ -37,12 +37,7 @@ func newRootCmd() *cobra.Command { Use: "virtwork", Short: "Create VMs on OpenShift with continuous workloads", } - - pf := rootCmd.PersistentFlags() - pf.String("namespace", "", "Kubernetes namespace for VMs") - pf.String("kubeconfig", "", "Path to kubeconfig file") - pf.String("config", "", "Path to YAML config file") - pf.Bool("verbose", false, "Enable verbose output") + config.BindPersistentFlags(rootCmd) runCmd := &cobra.Command{ Use: "run", @@ -51,20 +46,7 @@ func newRootCmd() *cobra.Command { return nil }, } - rf := runCmd.Flags() - rf.StringSlice("workloads", workloads.AllWorkloadNames(), "Workloads to deploy (comma-separated)") - rf.Int("vm-count", 1, "Number of VMs per workload") - rf.Int("cpu-cores", 0, "CPU cores per VM") - rf.String("memory", "", "Memory per VM (e.g., 2Gi)") - rf.String("disk-size", "", "Data disk size") - rf.String("container-disk-image", "", "Container disk image for VMs") - rf.Bool("dry-run", false, "Print specs without creating resources") - rf.Bool("no-wait", false, "Skip waiting for VM readiness") - rf.Int("timeout", 0, "Readiness timeout in seconds") - rf.String("ssh-user", "", "SSH user for VMs") - rf.String("ssh-password", "", "SSH password for VMs") - rf.StringSlice("ssh-key", nil, "SSH authorized key (repeatable)") - rf.StringSlice("ssh-key-file", nil, "SSH key file path (repeatable)") + config.BindRunFlags(runCmd, workloads.AllWorkloadNames()) cleanupCmd := &cobra.Command{ Use: "cleanup", @@ -73,10 +55,7 @@ func newRootCmd() *cobra.Command { return nil }, } - cleanupCmd.Flags().Bool("delete-namespace", false, "Also delete the namespace") - cleanupCmd.Flags().String("run-id", "", "Only delete resources from this specific run (UUID)") - cleanupCmd.Flags().Bool("dry-run", false, "Print intent without destroying resources") - cleanupCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and proceed with cleanup") + config.BindCleanupFlags(cleanupCmd) rootCmd.AddCommand(runCmd, cleanupCmd) return rootCmd diff --git a/internal/config/config.go b/internal/config/config.go index fcc4d13..5e739f1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -71,26 +71,46 @@ func SetDefaults(v *viper.Viper) { v.SetDefault("audit-db", constants.DefaultAuditDBPath) } -// BindFlags registers Cobra flags on the given command. -func BindFlags(cmd *cobra.Command) { +// BindPersistentFlags registers persistent flags shared across all subcommands. +func BindPersistentFlags(cmd *cobra.Command) { + pf := cmd.PersistentFlags() + pf.String("namespace", "", "Kubernetes namespace for VMs") + pf.String("kubeconfig", "", "Path to kubeconfig file") + pf.String("config", "", "Path to YAML config file") + pf.Bool("verbose", false, "Enable verbose output") + pf.Bool("audit", true, "Enable audit logging to SQLite") + pf.Bool("no-audit", false, "Disable audit logging") + pf.String("audit-db", "", "Path to audit database file") +} + +// BindRunFlags registers flags specific to the "run" subcommand. +// defaultWorkloads sets the default value for the --workloads flag. +func BindRunFlags(cmd *cobra.Command, defaultWorkloads []string) { f := cmd.Flags() - f.String("namespace", "", "Kubernetes namespace for VMs") - f.String("kubeconfig", "", "Path to kubeconfig file") - f.String("config", "", "Path to YAML config file") - f.String("container-disk-image", "", "Container disk image for VMs") - f.String("disk-size", "", "Data disk size") + f.StringSlice("workloads", defaultWorkloads, "Workloads to deploy (comma-separated)") + f.Int("vm-count", 1, "Number of VMs per workload") f.Int("cpu-cores", 0, "CPU cores per VM") f.String("memory", "", "Memory per VM (e.g., 2Gi)") + f.String("disk-size", "", "Data disk size") + f.String("container-disk-image", "", "Container disk image for VMs") f.Bool("dry-run", false, "Print specs without creating resources") f.Bool("no-wait", false, "Skip waiting for VM readiness") f.Int("timeout", 0, "Readiness timeout in seconds") - f.Bool("verbose", false, "Enable verbose output") f.String("ssh-user", "", "SSH user for VMs") f.String("ssh-password", "", "SSH password for VMs") f.StringSlice("ssh-key", nil, "SSH authorized key (repeatable)") f.StringSlice("ssh-key-file", nil, "SSH key file path (repeatable)") } +// BindCleanupFlags registers flags specific to the "cleanup" subcommand. +func BindCleanupFlags(cmd *cobra.Command) { + f := cmd.Flags() + f.Bool("delete-namespace", false, "Also delete the namespace") + f.String("run-id", "", "Only delete resources from this specific run (UUID)") + f.Bool("dry-run", false, "Print intent without destroying resources") + f.BoolP("yes", "y", false, "Skip confirmation prompt and proceed with cleanup") +} + // LoadConfig loads configuration from flags, environment variables, config file, // and defaults using the Viper priority chain: flags > env > file > defaults. func LoadConfig(cmd *cobra.Command) (*Config, error) { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 03d7a11..704357e 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -13,19 +13,31 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/opdev/virtwork/internal/config" "github.com/opdev/virtwork/internal/constants" ) func newTestCommand() *cobra.Command { + root := &cobra.Command{Use: "root"} + config.BindPersistentFlags(root) + cmd := &cobra.Command{ Use: "test", RunE: func(cmd *cobra.Command, args []string) error { return nil }, } - config.BindFlags(cmd) + config.BindRunFlags(cmd, nil) + root.AddCommand(cmd) + + // Merge root's persistent flags into cmd (Cobra does this during Execute; + // tests call LoadConfig directly so we replicate the merge here). + root.PersistentFlags().VisitAll(func(f *pflag.Flag) { + cmd.Flags().AddFlag(f) + }) + return cmd }