diff --git a/cmd/down.go b/cmd/down.go index fea6503a2..d010d3c69 100644 --- a/cmd/down.go +++ b/cmd/down.go @@ -16,11 +16,10 @@ var ( ) var downCmd = &cobra.Command{ - Use: "down", - Short: "Tear down the Windsor environment", - Long: "Tear down the Windsor environment by executing necessary shell commands.", - SilenceUsage: true, - PersistentPreRunE: checkTrust, + Use: "down", + Short: "Tear down the Windsor environment", + Long: "Tear down the Windsor environment by executing necessary shell commands.", + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) diff --git a/cmd/env.go b/cmd/env.go index 9199b66cb..55833ef1f 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -10,11 +10,10 @@ import ( ) var envCmd = &cobra.Command{ - Use: "env", - Short: "Output commands to set environment variables", - Long: "Output commands to set environment variables for the application.", - SilenceUsage: true, - PersistentPreRunE: checkTrust, + Use: "env", + Short: "Output commands to set environment variables", + Long: "Output commands to set environment variables for the application.", + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) diff --git a/cmd/env_test.go b/cmd/env_test.go index d42a1c768..d657b42d8 100644 --- a/cmd/env_test.go +++ b/cmd/env_test.go @@ -63,8 +63,9 @@ func TestEnvCmd(t *testing.T) { }) t.Run("SuccessWithHook", func(t *testing.T) { - // Given proper output capture + // Given proper output capture and mock setup _, stderr := setup(t) + setupMocks(t) rootCmd.SetArgs([]string{"env", "--hook"}) @@ -107,8 +108,9 @@ func TestEnvCmd(t *testing.T) { }) t.Run("SuccessWithAllFlags", func(t *testing.T) { - // Given proper output capture + // Given proper output capture and mock setup _, stderr := setup(t) + setupMocks(t) rootCmd.SetArgs([]string{"env", "--decrypt", "--hook", "--verbose"}) diff --git a/cmd/exec.go b/cmd/exec.go index d8352fb25..a65270161 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -11,11 +11,10 @@ import ( // execCmd represents the exec command var execCmd = &cobra.Command{ - Use: "exec [command] [args...]", - Short: "Execute a command with environment variables", - Long: "Execute a command with environment variables loaded from configuration and secrets", - Args: cobra.MinimumNArgs(1), - PersistentPreRunE: checkTrust, + Use: "exec [command] [args...]", + Short: "Execute a command with environment variables", + Long: "Execute a command with environment variables loaded from configuration and secrets", + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { // Safety check for arguments if len(args) == 0 { diff --git a/cmd/install.go b/cmd/install.go index bcacb5ea8..856e80496 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -12,10 +12,9 @@ import ( var installWaitFlag bool var installCmd = &cobra.Command{ - Use: "install", - Short: "Install the blueprint's cluster-level services", - SilenceUsage: true, - PersistentPreRunE: checkTrust, + Use: "install", + Short: "Install the blueprint's cluster-level services", + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) diff --git a/cmd/root.go b/cmd/root.go index 4043851a4..ab1abcef1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "github.com/spf13/cobra" @@ -32,22 +33,10 @@ func Execute() error { // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "windsor", - Short: "A command line interface to assist your cloud native development workflow", - Long: "A command line interface to assist your cloud native development workflow", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - // Set context from root command - ctx := cmd.Root().Context() - - // Add verbose flag to context if set - if verbose { - ctx = context.WithValue(ctx, "verbose", true) - } - - cmd.SetContext(ctx) - - return nil - }, + Use: "windsor", + Short: "A command line interface to assist your cloud native development workflow", + Long: "A command line interface to assist your cloud native development workflow", + PersistentPreRunE: commandPreflight, } func init() { @@ -55,22 +44,46 @@ func init() { rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output") } -// checkTrust performs trust validation for Windsor CLI commands requiring a trusted project directory. -// It verifies directory trust status by checking if the current project directory is in the trusted file list. -// For the "init" command, or for the "env" command with the --hook flag set, trust validation is skipped. -// Returns an error if the directory is untrusted. -func checkTrust(cmd *cobra.Command, args []string) error { - if cmd.Name() == "init" { - return nil +// commandPreflight orchestrates global CLI preflight checks and context initialization for all commands. +// Intended for use as cobra.Command.PersistentPreRunE, it ensures the command context is configured and +// the current directory is authorized for Windsor operations prior to command execution. +func commandPreflight(cmd *cobra.Command, args []string) error { + if err := setupGlobalContext(cmd); err != nil { + return err } + if err := enforceTrustedDirectory(cmd); err != nil { + return err + } + return nil +} - if cmd.Name() == "env" { - if hook, _ := cmd.Flags().GetBool("hook"); hook { - return nil - } +// setupGlobalContext injects global flags and context values into the command's context. +// It sets the verbose flag in the context if enabled. +func setupGlobalContext(cmd *cobra.Command) error { + ctx := cmd.Root().Context() + if ctx == nil { + ctx = context.Background() + } + if verbose { + ctx = context.WithValue(ctx, "verbose", true) + } + cmd.SetContext(ctx) + return nil +} + +// enforceTrustedDirectory checks if the current working directory is trusted for Windsor operations. +// Enforces trust for a defined set of commands, including "env". For "env" with --hook, exits silently to avoid shell integration noise. +// Returns an error if the directory is not trusted. +func enforceTrustedDirectory(cmd *cobra.Command) error { + const notTrustedDirMsg = "not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve" + enforcedCommands := []string{"up", "down", "exec", "install", "env"} + cmdName := cmd.Name() + shouldEnforce := slices.Contains(enforcedCommands, cmdName) + + if !shouldEnforce { + return nil } - // Use shims to allow mocking in tests currentDir, err := shims.Getwd() if err != nil { return fmt.Errorf("Error getting current directory: %w", err) @@ -87,18 +100,26 @@ func checkTrust(cmd *cobra.Command, args []string) error { data, err := shims.ReadFile(trustedFilePath) if err != nil { if os.IsNotExist(err) { - return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") + return fmt.Errorf(notTrustedDirMsg) } - return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") + return fmt.Errorf(notTrustedDirMsg) } - trustedDirs := strings.Split(strings.TrimSpace(string(data)), "\n") - for _, trustedDir := range trustedDirs { - trimmedDir := strings.TrimSpace(trustedDir) - if trimmedDir != "" && strings.HasPrefix(currentDir, trimmedDir) { + iter := strings.SplitSeq(strings.TrimSpace(string(data)), "\n") + + for trustedDir := range iter { + trustedDir = strings.TrimSpace(trustedDir) + if trustedDir != "" && strings.HasPrefix(currentDir, trustedDir) { return nil } } - return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve") + if cmdName == "env" { + hook, _ := cmd.Flags().GetBool("hook") + if hook { + shims.Exit(0) + } + } + + return fmt.Errorf(notTrustedDirMsg) } diff --git a/cmd/root_test.go b/cmd/root_test.go index aba58cbdd..bc74f55bb 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -268,7 +268,10 @@ func TestRootCmd_PersistentPreRunE(t *testing.T) { }) } -func TestCheckTrust(t *testing.T) { +func TestCommandPreflight(t *testing.T) { + // Set up mocks for all tests + setupMocks(t) + createMockCmd := func(name string) *cobra.Command { return &cobra.Command{ Use: name, @@ -280,7 +283,7 @@ func TestCheckTrust(t *testing.T) { cmd := createMockCmd("init") // When checking trust - err := checkTrust(cmd, []string{}) + err := commandPreflight(cmd, []string{}) // Then no error should occur (trust check is skipped) if err != nil { @@ -295,7 +298,7 @@ func TestCheckTrust(t *testing.T) { cmd.Flags().Set("hook", "true") // When checking trust - err := checkTrust(cmd, []string{}) + err := commandPreflight(cmd, []string{}) // Then no error should occur (trust check is skipped for env --hook) if err != nil { @@ -308,20 +311,23 @@ func TestCheckTrust(t *testing.T) { cmd := createMockCmd("env") cmd.Flags().Bool("hook", false, "hook flag") - // Set up a temporary directory that's not trusted + // Override shims to return an untrusted directory tmpDir := t.TempDir() - originalDir, err := os.Getwd() - if err != nil { - t.Fatalf("Failed to get current directory: %v", err) - } - defer os.Chdir(originalDir) - - if err := os.Chdir(tmpDir); err != nil { - t.Fatalf("Failed to change directory: %v", err) + origShims := shims + defer func() { shims = origShims }() + + shims = &Shims{ + Exit: func(int) {}, + UserHomeDir: func() (string, error) { return t.TempDir(), nil }, + Getwd: func() (string, error) { return tmpDir, nil }, + ReadFile: func(filename string) ([]byte, error) { + // Return trusted file content that does NOT include tmpDir + return []byte("/test/project\n"), nil + }, } // When checking trust - err = checkTrust(cmd, []string{}) + err := commandPreflight(cmd, []string{}) // Then an error should occur about untrusted directory if err == nil { @@ -380,7 +386,7 @@ func TestCheckTrust(t *testing.T) { } // When checking trust - err = checkTrust(cmd, []string{}) + err = commandPreflight(cmd, []string{}) // Then no error should occur if err != nil { diff --git a/cmd/up.go b/cmd/up.go index c1b4b4dd6..62136a108 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -15,11 +15,10 @@ var ( ) var upCmd = &cobra.Command{ - Use: "up", - Short: "Set up the Windsor environment", - Long: "Set up the Windsor environment by executing necessary shell commands.", - SilenceUsage: true, - PersistentPreRunE: checkTrust, + Use: "up", + Short: "Set up the Windsor environment", + Long: "Set up the Windsor environment by executing necessary shell commands.", + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { // Get shared dependency injector from context injector := cmd.Context().Value(injectorKey).(di.Injector) diff --git a/cmd/version.go b/cmd/version.go index c4648c2a7..44573c8e5 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -18,10 +18,9 @@ var Goos = runtime.GOOS // versionCmd represents the version command var versionCmd = &cobra.Command{ - Use: "version", - Short: "Display the current version", - Long: "Display the current version of the application", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return nil }, + Use: "version", + Short: "Display the current version", + Long: "Display the current version of the application", Run: func(cmd *cobra.Command, args []string) { platform := fmt.Sprintf("%s/%s", Goos, runtime.GOARCH) cmd.Printf("Version: %s\nCommit SHA: %s\nPlatform: %s\n", version, commitSHA, platform)