diff --git a/cmd/env.go b/cmd/env.go index 55833ef1f..1b5f9f77a 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "os" "github.com/spf13/cobra" "github.com/windsorcli/cli/pkg/di" @@ -22,6 +23,13 @@ var envCmd = &cobra.Command{ hook, _ := cmd.Flags().GetBool("hook") decrypt, _ := cmd.Flags().GetBool("decrypt") + // Set NO_CACHE=true unless --hook is specified or NO_CACHE is already set + if !hook && os.Getenv("NO_CACHE") == "" { + if err := os.Setenv("NO_CACHE", "true"); err != nil { + return fmt.Errorf("failed to set NO_CACHE environment variable: %w", err) + } + } + // Create execution context with flags ctx := cmd.Context() if decrypt { diff --git a/go.mod b/go.mod index 09e585473..41061b173 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( github.com/spf13/pflag v1.0.6 github.com/zclconf/go-cty v1.16.3 golang.org/x/crypto v0.40.0 - golang.org/x/sys v0.34.0 k8s.io/api v0.33.3 k8s.io/apimachinery v0.33.3 k8s.io/client-go v0.33.3 @@ -196,6 +195,7 @@ require ( golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect golang.org/x/term v0.33.0 // indirect golang.org/x/text v0.27.0 // indirect golang.org/x/time v0.11.0 // indirect diff --git a/pkg/env/aws_env_test.go b/pkg/env/aws_env_test.go index 74d27c8de..f8408c429 100644 --- a/pkg/env/aws_env_test.go +++ b/pkg/env/aws_env_test.go @@ -221,7 +221,7 @@ func TestAwsEnv_Print(t *testing.T) { // Mock PrintEnvVarsFunc to capture printed vars var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } diff --git a/pkg/env/azure_env_test.go b/pkg/env/azure_env_test.go index 214e6f028..cfc796567 100644 --- a/pkg/env/azure_env_test.go +++ b/pkg/env/azure_env_test.go @@ -142,7 +142,7 @@ func TestAzureEnv_Print(t *testing.T) { t.Fatalf("Failed to get config root: %v", err) } var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } err = printer.Print() diff --git a/pkg/env/docker_env_test.go b/pkg/env/docker_env_test.go index dbaa4a6da..a3ad6b020 100644 --- a/pkg/env/docker_env_test.go +++ b/pkg/env/docker_env_test.go @@ -655,7 +655,7 @@ func TestDockerEnvPrinter_Print(t *testing.T) { // And PrintEnvVarsFunc is mocked var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } diff --git a/pkg/env/env.go b/pkg/env/env.go index 0d69d73e9..8305f0ef0 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -91,7 +91,7 @@ func (e *BaseEnvPrinter) Print(customVars ...map[string]string) error { e.SetManagedEnv(key) } - e.shell.PrintEnvVars(envVars) + e.shell.PrintEnvVars(envVars, true) return nil } diff --git a/pkg/env/env_test.go b/pkg/env/env_test.go index 73cd5b21f..75c37a577 100644 --- a/pkg/env/env_test.go +++ b/pkg/env/env_test.go @@ -230,7 +230,7 @@ func TestEnv_Print(t *testing.T) { // And a mock PrintEnvVarsFunc var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } @@ -256,7 +256,7 @@ func TestEnv_Print(t *testing.T) { // And a mock PrintEnvVarsFunc var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } diff --git a/pkg/env/omni_env_test.go b/pkg/env/omni_env_test.go index 6abf27e25..27af9aad7 100644 --- a/pkg/env/omni_env_test.go +++ b/pkg/env/omni_env_test.go @@ -138,7 +138,7 @@ func TestOmniEnvPrinter_Print(t *testing.T) { // And PrintEnvVarsFunc is mocked var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } diff --git a/pkg/env/talos_env_test.go b/pkg/env/talos_env_test.go index e16e77e45..33335afb9 100644 --- a/pkg/env/talos_env_test.go +++ b/pkg/env/talos_env_test.go @@ -141,7 +141,7 @@ func TestTalosEnv_Print(t *testing.T) { // And PrintEnvVarsFunc is mocked var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } diff --git a/pkg/env/terraform_env_test.go b/pkg/env/terraform_env_test.go index c13290cae..ff712d3f1 100644 --- a/pkg/env/terraform_env_test.go +++ b/pkg/env/terraform_env_test.go @@ -450,7 +450,7 @@ func TestTerraformEnv_Print(t *testing.T) { printer, mocks := setup(t) var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } diff --git a/pkg/env/windsor_env.go b/pkg/env/windsor_env.go index 5e98b76f2..440cbe9af 100644 --- a/pkg/env/windsor_env.go +++ b/pkg/env/windsor_env.go @@ -206,7 +206,8 @@ func (e *WindsorEnvPrinter) parseAndCheckSecrets(strValue string) string { return strValue } -// shouldUseCache determines if the cache should be used based on the current and Windsor context. +// shouldUseCache determines if the cache should be used based on NO_CACHE environment variable. +// Cache is enabled by default and can be disabled by setting NO_CACHE=1 or NO_CACHE=true. func (e *WindsorEnvPrinter) shouldUseCache() bool { noCache, _ := e.shims.LookupEnv("NO_CACHE") return noCache == "" || noCache == "0" || noCache == "false" || noCache == "False" diff --git a/pkg/env/windsor_env_test.go b/pkg/env/windsor_env_test.go index 54d3b0e03..cbee03900 100644 --- a/pkg/env/windsor_env_test.go +++ b/pkg/env/windsor_env_test.go @@ -821,7 +821,7 @@ func TestWindsorEnv_Print(t *testing.T) { // And a mock PrintEnvVars function var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } diff --git a/pkg/pipelines/env.go b/pkg/pipelines/env.go index bfdf7fd02..0bda1dd35 100644 --- a/pkg/pipelines/env.go +++ b/pkg/pipelines/env.go @@ -137,7 +137,7 @@ func (p *EnvPipeline) Execute(ctx context.Context) error { } if !quiet { - p.shell.PrintEnvVars(allEnvVars) + p.shell.PrintEnvVars(allEnvVars, hook) var firstError error for _, envPrinter := range p.envPrinters { diff --git a/pkg/pipelines/env_test.go b/pkg/pipelines/env_test.go index bf7cb4e99..352f7e3f2 100644 --- a/pkg/pipelines/env_test.go +++ b/pkg/pipelines/env_test.go @@ -42,7 +42,7 @@ func setupEnvMocks(t *testing.T, opts ...*SetupOptions) *EnvMocks { baseMocks.Shell.CheckTrustedDirectoryFunc = func() error { return nil } baseMocks.Shell.CheckResetFlagsFunc = func() (bool, error) { return false, nil } baseMocks.Shell.GetSessionTokenFunc = func() (string, error) { return "test-token", nil } - baseMocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) {} + baseMocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) {} baseMocks.Shell.ResetFunc = func(args ...bool) {} return &EnvMocks{ @@ -521,7 +521,7 @@ func TestEnvPipeline_Execute(t *testing.T) { } printCalled := false - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { printCalled = true } @@ -562,7 +562,7 @@ func TestEnvPipeline_Execute(t *testing.T) { } printCalled := false - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { printCalled = true } @@ -594,7 +594,7 @@ func TestEnvPipeline_Execute(t *testing.T) { return nil } - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) {} + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) {} mockEnvPrinter := env.NewMockEnvPrinter() mockEnvPrinter.PostEnvHookFunc = func(directory ...string) error { @@ -628,7 +628,7 @@ func TestEnvPipeline_Execute(t *testing.T) { return nil } - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) {} + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) {} mockEnvPrinter := env.NewMockEnvPrinter() mockEnvPrinter.PostEnvHookFunc = func(directory ...string) error { @@ -780,7 +780,7 @@ func TestEnvPipeline_Execute(t *testing.T) { pipeline.envPrinters = []env.EnvPrinter{mockEnvPrinter1, mockEnvPrinter2} var capturedEnvVars map[string]string - mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { capturedEnvVars = envVars } diff --git a/pkg/shell/mock_shell.go b/pkg/shell/mock_shell.go index 53f075225..ed2ec7e30 100644 --- a/pkg/shell/mock_shell.go +++ b/pkg/shell/mock_shell.go @@ -18,7 +18,7 @@ import ( type MockShell struct { DefaultShell InitializeFunc func() error - PrintEnvVarsFunc func(envVars map[string]string) + PrintEnvVarsFunc func(envVars map[string]string, export bool) PrintAliasFunc func(envVars map[string]string) GetProjectRootFunc func() (string, error) ExecFunc func(command string, args ...string) (string, error) @@ -71,9 +71,9 @@ func (s *MockShell) Initialize() error { } // PrintEnvVars calls the custom PrintEnvVarsFunc if provided. -func (s *MockShell) PrintEnvVars(envVars map[string]string) { +func (s *MockShell) PrintEnvVars(envVars map[string]string, export bool) { if s.PrintEnvVarsFunc != nil { - s.PrintEnvVarsFunc(envVars) + s.PrintEnvVarsFunc(envVars, export) } } diff --git a/pkg/shell/mock_shell_test.go b/pkg/shell/mock_shell_test.go index 6d4c08798..c9948ef9f 100644 --- a/pkg/shell/mock_shell_test.go +++ b/pkg/shell/mock_shell_test.go @@ -1029,7 +1029,7 @@ func TestMockShell_PrintEnvVars(t *testing.T) { mockShell := setupMockShellMocks(t) called := false expectedEnvVars := map[string]string{"TEST": "value"} - mockShell.PrintEnvVarsFunc = func(envVars map[string]string) { + mockShell.PrintEnvVarsFunc = func(envVars map[string]string, export bool) { called = true if envVars["TEST"] != "value" { t.Errorf("Expected envVars[TEST] = value, got %v", envVars["TEST"]) @@ -1037,7 +1037,7 @@ func TestMockShell_PrintEnvVars(t *testing.T) { } // When PrintEnvVars is called - mockShell.PrintEnvVars(expectedEnvVars) + mockShell.PrintEnvVars(expectedEnvVars, true) // Then the mock function should be called if !called { @@ -1051,7 +1051,7 @@ func TestMockShell_PrintEnvVars(t *testing.T) { // When PrintEnvVars is called // Then it should not panic - mockShell.PrintEnvVars(map[string]string{"TEST": "value"}) + mockShell.PrintEnvVars(map[string]string{"TEST": "value"}, false) }) } diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go index 03f73ba75..0ee20682a 100644 --- a/pkg/shell/shell.go +++ b/pkg/shell/shell.go @@ -8,6 +8,7 @@ import ( "path" "path/filepath" "slices" + "sort" "strings" "sync" @@ -46,7 +47,7 @@ type HookContext struct { type Shell interface { Initialize() error SetVerbosity(verbose bool) - PrintEnvVars(envVars map[string]string) + PrintEnvVars(envVars map[string]string, export bool) PrintAlias(envVars map[string]string) GetProjectRoot() (string, error) Exec(command string, args ...string) (string, error) @@ -668,6 +669,33 @@ func (s *DefaultShell) scrubString(input string) string { return result } +// PrintEnvVars is a platform-specific method that will be implemented by Unix/Windows-specific files +// The export parameter controls whether to use OS-specific export commands or plain KEY=value format +func (s *DefaultShell) PrintEnvVars(envVars map[string]string, export bool) { + if export { + s.printEnvVarsWithExport(envVars) + } else { + s.printEnvVarsPlain(envVars) + } +} + +// printEnvVarsPlain prints environment variables in plain KEY=value format, sorted by key. +// If a value is empty, it prints KEY= with no value. Used for non-export output scenarios. +func (s *DefaultShell) printEnvVarsPlain(envVars map[string]string) { + keys := make([]string, 0, len(envVars)) + for k := range envVars { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + if envVars[k] == "" { + fmt.Printf("%s=\n", k) + } else { + fmt.Printf("%s=%s\n", k, envVars[k]) + } + } +} + // Ensure DefaultShell implements the Shell interface var _ Shell = (*DefaultShell)(nil) diff --git a/pkg/shell/unix_shell.go b/pkg/shell/unix_shell.go index b6f2965be..f4caf7c72 100644 --- a/pkg/shell/unix_shell.go +++ b/pkg/shell/unix_shell.go @@ -18,76 +18,56 @@ import ( // Public Methods // ============================================================================= -// PrintEnvVars prints the provided environment variables in a sorted order. -// If the value of an environment variable is an empty string, it will print an unset command. -func (s *DefaultShell) PrintEnvVars(envVars map[string]string) { - // Create a slice to hold the keys of the envVars map +// printEnvVarsWithExport prints environment variables in sorted order using export commands. +// If a variable's value is empty, it prints an unset command instead. +func (s *DefaultShell) printEnvVarsWithExport(envVars map[string]string) { keys := make([]string, 0, len(envVars)) - - // Append each key from the envVars map to the keys slice for k := range envVars { keys = append(keys, k) } - - // Sort the keys slice to ensure the environment variables are printed in order sort.Strings(keys) - - // Iterate over the sorted keys and print the corresponding environment variable for _, k := range keys { if envVars[k] == "" { - // Print unset command if the value is an empty string fmt.Printf("unset %s\n", k) } else { - // Print export command with the key and value fmt.Printf("export %s=\"%s\"\n", k, envVars[k]) } } } -// PrintAlias prints the aliases for the shell. +// PrintAlias prints sorted aliases. Empty values print unalias; non-empty print alias with key and value. func (s *DefaultShell) PrintAlias(aliases map[string]string) { - // Create a slice to hold the keys of the aliases map keys := make([]string, 0, len(aliases)) - - // Append each key from the aliases map to the keys slice for k := range aliases { keys = append(keys, k) } - - // Sort the keys slice to ensure the aliases are printed in order sort.Strings(keys) - - // Iterate over the sorted keys and print the corresponding alias for _, k := range keys { if aliases[k] == "" { - // Print unset command if the value is an empty string fmt.Printf("unalias %s\n", k) } else { - // Print alias command with the key and value fmt.Printf("alias %s=\"%s\"\n", k, aliases[k]) } } } -// UnsetEnvs generates a command to unset multiple environment variables. -// For Unix shells, this produces a single 'unset' command with all variables in one line. +// UnsetEnvs generates a single unset command for multiple environment variables in Unix shells. +// It prints a single 'unset' command with all provided variable names separated by spaces. +// If the input slice is empty, no output is produced. func (s *DefaultShell) UnsetEnvs(envVars []string) { if len(envVars) == 0 { return } - - // Create a single unset command with all environment variables fmt.Printf("unset %s\n", strings.Join(envVars, " ")) } -// UnsetAlias generates commands to unset multiple aliases. -// For Unix shells, this produces a separate 'unalias' command for each alias. +// UnsetAlias generates individual unalias commands for each alias in Unix shells. +// It prints a separate 'unalias' command for each alias name provided. +// If the input slice is empty, no output is produced. func (s *DefaultShell) UnsetAlias(aliases []string) { if len(aliases) == 0 { return } - - // Print individual unalias commands for each alias for _, alias := range aliases { fmt.Printf("unalias %s\n", alias) } diff --git a/pkg/shell/unix_shell_test.go b/pkg/shell/unix_shell_test.go index da248b02d..4bd27d3aa 100644 --- a/pkg/shell/unix_shell_test.go +++ b/pkg/shell/unix_shell_test.go @@ -42,7 +42,7 @@ func TestDefaultShell_PrintEnvVars(t *testing.T) { // When capturing the output of PrintEnvVars output := captureStdout(t, func() { - shell.PrintEnvVars(envVars) + shell.PrintEnvVars(envVars, true) }) // Then the output should match the expected output diff --git a/pkg/shell/windows_shell.go b/pkg/shell/windows_shell.go index 6a117cb10..d765fb8a2 100644 --- a/pkg/shell/windows_shell.go +++ b/pkg/shell/windows_shell.go @@ -17,8 +17,8 @@ import ( // Public Methods // ============================================================================= -// PrintEnvVars sorts and prints environment variables. Empty values trigger a removal command. -func (s *DefaultShell) PrintEnvVars(envVars map[string]string) { +// printEnvVarsWithExport sorts and prints environment variables with PowerShell commands. Empty values trigger a removal command. +func (s *DefaultShell) printEnvVarsWithExport(envVars map[string]string) { keys := make([]string, 0, len(envVars)) for k := range envVars { keys = append(keys, k) diff --git a/pkg/shell/windows_shell_test.go b/pkg/shell/windows_shell_test.go index 58cc6f7ed..45c2e369e 100644 --- a/pkg/shell/windows_shell_test.go +++ b/pkg/shell/windows_shell_test.go @@ -5,13 +5,10 @@ package shell import ( "fmt" - "io" "os" "path/filepath" "strings" "testing" - - "golang.org/x/sys/windows" ) // The WindowsShellTest is a test suite for Windows-specific shell operations. @@ -19,35 +16,6 @@ import ( // project root detection, and alias handling on Windows systems. // The WindowsShellTest ensures reliable shell operations on Windows platforms. -// ============================================================================= -// Test Setup -// ============================================================================= - -// Helper function to get the long path name on Windows -// This function converts a short path to its long form -func getLongPathName(shortPath string) (string, error) { - p, err := windows.UTF16PtrFromString(shortPath) - if err != nil { - return "", err - } - b := make([]uint16, windows.MAX_LONG_PATH) - r, err := windows.GetLongPathName(p, &b[0], uint32(len(b))) - if r == 0 { - return "", err - } - return windows.UTF16ToString(b), nil -} - -// Helper function to normalize a Windows path -// This function ensures the path is in its long form and normalized -func normalizeWindowsPath(path string) string { - longPath, err := getLongPathName(path) - if err != nil { - return normalizePath(path) - } - return normalizePath(longPath) -} - // ============================================================================= // Test Public Methods // ============================================================================= @@ -74,7 +42,7 @@ func TestDefaultShell_PrintEnvVars(t *testing.T) { // When capturing the output of PrintEnvVars output := captureStdout(t, func() { - shell.PrintEnvVars(envVars) + shell.PrintEnvVars(envVars, true) }) // Then the output should match the expected output @@ -280,50 +248,3 @@ func TestDefaultShell_UnsetAlias(t *testing.T) { } }) } - -// ============================================================================= -// Helper Functions -// ============================================================================= - -// Helper function to change the current directory -func changeDir(t *testing.T, dir string) { - t.Helper() - if err := os.Chdir(dir); err != nil { - t.Fatalf("Failed to change directory to %s: %v", dir, err) - } -} - -// Helper function to normalize a path for comparison -func normalizePath(path string) string { - return filepath.Clean(path) -} - -// Helper function to capture stdout from a function -func captureStdoutFromFunc(t *testing.T, fn func()) string { - t.Helper() - - // Create a pipe to capture stdout - oldStdout := os.Stdout - r, w, err := os.Pipe() - if err != nil { - t.Fatalf("Failed to create pipe: %v", err) - } - os.Stdout = w - - // Run the function - fn() - - // Close the writer - w.Close() - - // Restore stdout - os.Stdout = oldStdout - - // Read the output - buf, err := io.ReadAll(r) - if err != nil { - t.Fatalf("Failed to read from pipe: %v", err) - } - - return string(buf) -}