From 5921e27bd46df388a9cfe96eee84549f56b87f80 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Wed, 22 Oct 2025 08:12:49 -0400 Subject: [PATCH 1/3] Add LoadEnvPrinters to runtime Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- pkg/runtime/runtime.go | 42 +++++ pkg/runtime/runtime_test.go | 334 ++++++++++++++++++++++++++++++++++++ 2 files changed, 376 insertions(+) diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index c55164397..a7170dec0 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -136,6 +136,48 @@ func (r *Runtime) LoadConfigHandler() *Runtime { return r } +// LoadEnvPrinters loads and initializes the environment printers. +func (r *Runtime) LoadEnvPrinters() *Runtime { + if r.err != nil { + return r + } + if r.ConfigHandler == nil { + r.err = fmt.Errorf("config handler not loaded - call LoadConfigHandler() first") + return r + } + if r.EnvPrinters.AwsEnv == nil && r.ConfigHandler.GetBool("aws.enabled", false) { + r.EnvPrinters.AwsEnv = env.NewAwsEnvPrinter(r.Injector) + r.Injector.Register("awsEnv", r.EnvPrinters.AwsEnv) + } + if r.EnvPrinters.AzureEnv == nil && r.ConfigHandler.GetBool("azure.enabled", false) { + r.EnvPrinters.AzureEnv = env.NewAzureEnvPrinter(r.Injector) + r.Injector.Register("azureEnv", r.EnvPrinters.AzureEnv) + } + if r.EnvPrinters.DockerEnv == nil && r.ConfigHandler.GetBool("docker.enabled", false) { + r.EnvPrinters.DockerEnv = env.NewDockerEnvPrinter(r.Injector) + r.Injector.Register("dockerEnv", r.EnvPrinters.DockerEnv) + } + if r.EnvPrinters.KubeEnv == nil && r.ConfigHandler.GetBool("cluster.enabled", false) { + r.EnvPrinters.KubeEnv = env.NewKubeEnvPrinter(r.Injector) + r.Injector.Register("kubeEnv", r.EnvPrinters.KubeEnv) + } + if r.EnvPrinters.TalosEnv == nil && + (r.ConfigHandler.GetString("cluster.driver", "") == "talos" || + r.ConfigHandler.GetString("cluster.driver", "") == "omni") { + r.EnvPrinters.TalosEnv = env.NewTalosEnvPrinter(r.Injector) + r.Injector.Register("talosEnv", r.EnvPrinters.TalosEnv) + } + if r.EnvPrinters.TerraformEnv == nil && r.ConfigHandler.GetBool("terraform.enabled", false) { + r.EnvPrinters.TerraformEnv = env.NewTerraformEnvPrinter(r.Injector) + r.Injector.Register("terraformEnv", r.EnvPrinters.TerraformEnv) + } + if r.EnvPrinters.WindsorEnv == nil { + r.EnvPrinters.WindsorEnv = env.NewWindsorEnvPrinter(r.Injector) + r.Injector.Register("windsorEnv", r.EnvPrinters.WindsorEnv) + } + return r +} + // InstallHook installs a shell hook for the specified shell type. func (r *Runtime) InstallHook(shellType string) *Runtime { if r.err != nil { diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index d9657d62b..8cc1ce58b 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -231,6 +231,340 @@ func TestRuntime_LoadConfigHandler(t *testing.T) { }) } +func TestRuntime_LoadEnvPrinters(t *testing.T) { + t.Run("LoadsEnvPrintersSuccessfully", func(t *testing.T) { + // Given a runtime with loaded shell and config handler + mocks := setupMocks(t) + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And WindsorEnv should always be loaded + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + + // And WindsorEnv should be registered in injector + resolvedWindsorEnv := runtime.Injector.Resolve("windsorEnv") + if resolvedWindsorEnv == nil { + t.Error("Expected WindsorEnv to be registered in injector") + } + }) + + t.Run("ReturnsErrorWhenConfigHandlerNotLoaded", func(t *testing.T) { + // Given a runtime without loaded config handler (no pre-loaded dependencies) + runtime := NewRuntime() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And error should be set + if runtime.err == nil { + t.Error("Expected error when config handler not loaded") + } + + expectedError := "config handler not loaded - call LoadConfigHandler() first" + if runtime.err.Error() != expectedError { + t.Errorf("Expected error %q, got %q", expectedError, runtime.err.Error()) + } + }) + + t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { + // Given a runtime with an existing error (no pre-loaded dependencies) + runtime := NewRuntime() + runtime.err = errors.New("existing error") + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And original error should be preserved + if runtime.err.Error() != "existing error" { + t.Errorf("Expected original error to be preserved, got %v", runtime.err) + } + + // And no env printers should be loaded + if runtime.EnvPrinters.WindsorEnv != nil { + t.Error("Expected no env printers to be loaded when error exists") + } + }) + + t.Run("LoadsOnlyEnabledEnvPrinters", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with specific enabled features + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetBoolFunc = func(key string, defaultValue ...bool) bool { + switch key { + case "aws.enabled": + return true + case "azure.enabled": + return false + case "docker.enabled": + return true + case "cluster.enabled": + return false + case "terraform.enabled": + return true + default: + if len(defaultValue) > 0 { + return defaultValue[0] + } + return false + } + } + mocks.ConfigHandler.(*config.MockConfigHandler).GetStringFunc = func(key string, defaultValue ...string) string { + if key == "cluster.driver" { + return "kubernetes" + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return "" + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And enabled env printers should be loaded + if runtime.EnvPrinters.AwsEnv == nil { + t.Error("Expected AwsEnv to be loaded when enabled") + } + if runtime.EnvPrinters.DockerEnv == nil { + t.Error("Expected DockerEnv to be loaded when enabled") + } + if runtime.EnvPrinters.TerraformEnv == nil { + t.Error("Expected TerraformEnv to be loaded when enabled") + } + + // And disabled env printers should not be loaded + if runtime.EnvPrinters.AzureEnv != nil { + t.Error("Expected AzureEnv to not be loaded when disabled") + } + if runtime.EnvPrinters.KubeEnv != nil { + t.Error("Expected KubeEnv to not be loaded when disabled") + } + if runtime.EnvPrinters.TalosEnv != nil { + t.Error("Expected TalosEnv to not be loaded when cluster driver is not talos/omni") + } + + // And WindsorEnv should always be loaded + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + }) + + t.Run("LoadsWindsorEnvPrinterAlways", func(t *testing.T) { + // Given a runtime with loaded shell and config handler + mocks := setupMocks(t) + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And WindsorEnv should always be loaded regardless of config + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + + // And WindsorEnv should be registered in injector + resolvedWindsorEnv := runtime.Injector.Resolve("windsorEnv") + if resolvedWindsorEnv == nil { + t.Error("Expected WindsorEnv to be registered in injector") + } + }) + + t.Run("LoadsTalosEnvPrinterForTalosDriver", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with talos driver + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetStringFunc = func(key string, defaultValue ...string) string { + if key == "cluster.driver" { + return "talos" + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return "" + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And TalosEnv should be loaded for talos driver + if runtime.EnvPrinters.TalosEnv == nil { + t.Error("Expected TalosEnv to be loaded for talos driver") + } + + // And TalosEnv should be registered in injector + resolvedTalosEnv := runtime.Injector.Resolve("talosEnv") + if resolvedTalosEnv == nil { + t.Error("Expected TalosEnv to be registered in injector") + } + }) + + t.Run("LoadsTalosEnvPrinterForOmniDriver", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with omni driver + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetStringFunc = func(key string, defaultValue ...string) string { + if key == "cluster.driver" { + return "omni" + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return "" + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And TalosEnv should be loaded for omni driver + if runtime.EnvPrinters.TalosEnv == nil { + t.Error("Expected TalosEnv to be loaded for omni driver") + } + + // And TalosEnv should be registered in injector + resolvedTalosEnv := runtime.Injector.Resolve("talosEnv") + if resolvedTalosEnv == nil { + t.Error("Expected TalosEnv to be registered in injector") + } + }) + + t.Run("LoadsAzureEnvPrinterWhenEnabled", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with azure enabled + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "azure.enabled" { + return true + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return false + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And AzureEnv should be loaded when enabled + if runtime.EnvPrinters.AzureEnv == nil { + t.Error("Expected AzureEnv to be loaded when enabled") + } + + // And AzureEnv should be registered in injector + resolvedAzureEnv := runtime.Injector.Resolve("azureEnv") + if resolvedAzureEnv == nil { + t.Error("Expected AzureEnv to be registered in injector") + } + + // And WindsorEnv should always be loaded + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + }) + + t.Run("LoadsKubeEnvPrinterWhenEnabled", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with cluster enabled + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "cluster.enabled" { + return true + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return false + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And KubeEnv should be loaded when cluster is enabled + if runtime.EnvPrinters.KubeEnv == nil { + t.Error("Expected KubeEnv to be loaded when cluster is enabled") + } + + // And KubeEnv should be registered in injector + resolvedKubeEnv := runtime.Injector.Resolve("kubeEnv") + if resolvedKubeEnv == nil { + t.Error("Expected KubeEnv to be registered in injector") + } + + // And WindsorEnv should always be loaded + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + }) +} + func TestRuntime_InstallHook(t *testing.T) { t.Run("InstallsHookSuccessfully", func(t *testing.T) { // Given a runtime with loaded shell From f162e8f5bc490c7b40d32d20d57fa6bbe6a5ef5a Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:35:57 -0400 Subject: [PATCH 2/3] Add LoadSecretsProviders Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- pkg/runtime/runtime.go | 57 +++++++- pkg/runtime/runtime_test.go | 282 ++++++++++++++++++++++++++++++++++++ pkg/runtime/shims.go | 22 +++ 3 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 pkg/runtime/shims.go diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index a7170dec0..fba1fed08 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -2,7 +2,9 @@ package runtime import ( "fmt" + "path/filepath" + secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" "github.com/windsorcli/cli/pkg/artifact" "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/cluster" @@ -71,7 +73,8 @@ type Dependencies struct { // Runtime encapsulates all core Windsor CLI runtime dependencies. type Runtime struct { Dependencies - err error + Shims *Shims + err error } // ============================================================================= @@ -91,6 +94,7 @@ func NewRuntime(deps ...*Dependencies) *Runtime { } return &Runtime{ Dependencies: *depsVal, + Shims: NewShims(), } } @@ -178,6 +182,57 @@ func (r *Runtime) LoadEnvPrinters() *Runtime { return r } +// LoadSecretsProviders loads and initializes the secrets providers using configuration and environment. +// It detects SOPS and 1Password vaults as in BasePipeline.withSecretsProviders. +func (r *Runtime) LoadSecretsProviders() *Runtime { + if r.err != nil { + return r + } + if r.ConfigHandler == nil { + r.err = fmt.Errorf("config handler not loaded - call LoadConfigHandler() first") + return r + } + + configRoot, err := r.ConfigHandler.GetConfigRoot() + if err != nil { + r.err = fmt.Errorf("error getting config root: %w", err) + return r + } + + secretsFilePaths := []string{"secrets.enc.yaml", "secrets.enc.yml"} + for _, filePath := range secretsFilePaths { + if _, err := r.Shims.Stat(filepath.Join(configRoot, filePath)); err == nil { + if r.SecretsProviders.Sops == nil { + r.SecretsProviders.Sops = secrets.NewSopsSecretsProvider(configRoot, r.Injector) + r.Injector.Register("sopsSecretsProvider", r.SecretsProviders.Sops) + } + break + } + } + + vaults, ok := r.ConfigHandler.Get("secrets.onepassword.vaults").(map[string]secretsConfigType.OnePasswordVault) + if ok && len(vaults) > 0 { + useSDK := r.Shims.Getenv("OP_SERVICE_ACCOUNT_TOKEN") != "" + + for key, vault := range vaults { + vaultCopy := vault + vaultCopy.ID = key + + if r.SecretsProviders.Onepassword == nil { + if useSDK { + r.SecretsProviders.Onepassword = secrets.NewOnePasswordSDKSecretsProvider(vaultCopy, r.Injector) + } else { + r.SecretsProviders.Onepassword = secrets.NewOnePasswordCLISecretsProvider(vaultCopy, r.Injector) + } + r.Injector.Register("onePasswordSecretsProvider", r.SecretsProviders.Onepassword) + break + } + } + } + + return r +} + // InstallHook installs a shell hook for the specified shell type. func (r *Runtime) InstallHook(shellType string) *Runtime { if r.err != nil { diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 8cc1ce58b..c4ecda282 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -2,9 +2,11 @@ package runtime import ( "errors" + "os" "strings" "testing" + secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" "github.com/windsorcli/cli/pkg/shell" @@ -565,6 +567,286 @@ func TestRuntime_LoadEnvPrinters(t *testing.T) { }) } +func TestRuntime_LoadSecretsProviders(t *testing.T) { + t.Run("LoadsSecretsProvidersSuccessfully", func(t *testing.T) { + // Given a runtime with loaded shell and config handler + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + }) + + t.Run("ReturnsErrorWhenConfigHandlerNotLoaded", func(t *testing.T) { + // Given a runtime without loaded config handler (no pre-loaded dependencies) + runtime := NewRuntime() + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And error should be set + if runtime.err == nil { + t.Error("Expected error when config handler not loaded") + } + + expectedError := "config handler not loaded - call LoadConfigHandler() first" + if runtime.err.Error() != expectedError { + t.Errorf("Expected error %q, got %q", expectedError, runtime.err.Error()) + } + }) + + t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { + // Given a runtime with an existing error (no pre-loaded dependencies) + runtime := NewRuntime() + runtime.err = errors.New("existing error") + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And original error should be preserved + if runtime.err.Error() != "existing error" { + t.Errorf("Expected original error to be preserved, got %v", runtime.err) + } + + // And no secrets providers should be loaded + if runtime.SecretsProviders.Sops != nil { + t.Error("Expected no secrets providers to be loaded when error exists") + } + if runtime.SecretsProviders.Onepassword != nil { + t.Error("Expected no secrets providers to be loaded when error exists") + } + }) + + t.Run("PropagatesConfigRootError", func(t *testing.T) { + // Given a runtime with loaded shell and config handler that returns error for GetConfigRoot + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "", errors.New("config root error") + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And error should be propagated + if runtime.err == nil { + t.Error("Expected error to be propagated from config root") + } else { + expectedError := "error getting config root" + if !strings.Contains(runtime.err.Error(), expectedError) { + t.Errorf("Expected error to contain %q, got %q", expectedError, runtime.err.Error()) + } + } + }) + + t.Run("LoadsSopsProviderWhenSecretsFileExists", func(t *testing.T) { + // Given a runtime with loaded shell and config handler, and secrets file exists + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // Mock Stat to return success for secrets.enc.yaml + runtime.Shims.Stat = func(name string) (os.FileInfo, error) { + if strings.Contains(name, "secrets.enc.yaml") { + return nil, nil // Success - file exists + } + return nil, errors.New("file not found") + } + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And Sops provider should be loaded + if runtime.SecretsProviders.Sops == nil { + t.Error("Expected Sops provider to be loaded when secrets file exists") + } + + // And Sops provider should be registered in injector + resolvedSops := runtime.Injector.Resolve("sopsSecretsProvider") + if resolvedSops == nil { + t.Error("Expected Sops provider to be registered in injector") + } + }) + + t.Run("LoadsOnePasswordSDKProviderWhenTokenExists", func(t *testing.T) { + // Given a runtime with loaded shell and config handler, OnePassword vaults configured, and SDK token + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + mocks.ConfigHandler.(*config.MockConfigHandler).GetFunc = func(key string) any { + if key == "secrets.onepassword.vaults" { + return map[string]secretsConfigType.OnePasswordVault{ + "vault1": {URL: "https://vault1.com", Name: "Vault 1"}, + } + } + return nil + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // Mock Getenv to return SDK token + runtime.Shims.Getenv = func(key string) string { + if key == "OP_SERVICE_ACCOUNT_TOKEN" { + return "test-token" + } + return "" + } + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And OnePassword provider should be loaded + if runtime.SecretsProviders.Onepassword == nil { + t.Error("Expected OnePassword provider to be loaded when vaults configured and SDK token exists") + } + + // And OnePassword provider should be registered in injector + resolvedOnePassword := runtime.Injector.Resolve("onePasswordSecretsProvider") + if resolvedOnePassword == nil { + t.Error("Expected OnePassword provider to be registered in injector") + } + }) + + t.Run("LoadsOnePasswordCLIProviderWhenNoToken", func(t *testing.T) { + // Given a runtime with loaded shell and config handler, OnePassword vaults configured, but no SDK token + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + mocks.ConfigHandler.(*config.MockConfigHandler).GetFunc = func(key string) any { + if key == "secrets.onepassword.vaults" { + return map[string]secretsConfigType.OnePasswordVault{ + "vault1": {URL: "https://vault1.com", Name: "Vault 1"}, + } + } + return nil + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // Mock Getenv to return no SDK token + runtime.Shims.Getenv = func(key string) string { + return "" + } + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And OnePassword provider should be loaded + if runtime.SecretsProviders.Onepassword == nil { + t.Error("Expected OnePassword provider to be loaded when vaults configured but no SDK token") + } + + // And OnePassword provider should be registered in injector + resolvedOnePassword := runtime.Injector.Resolve("onePasswordSecretsProvider") + if resolvedOnePassword == nil { + t.Error("Expected OnePassword provider to be registered in injector") + } + }) + + t.Run("DoesNotLoadProvidersWhenNoConfig", func(t *testing.T) { + // Given a runtime with loaded shell and config handler, but no secrets configuration + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + mocks.ConfigHandler.(*config.MockConfigHandler).GetFunc = func(key string) any { + return nil // No secrets configuration + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // Mock Stat to return file not found for secrets files + runtime.Shims.Stat = func(name string) (os.FileInfo, error) { + return nil, errors.New("file not found") + } + + // Mock Getenv to return no SDK token + runtime.Shims.Getenv = func(key string) string { + return "" + } + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And no secrets providers should be loaded + if runtime.SecretsProviders.Sops != nil { + t.Error("Expected Sops provider to not be loaded when no secrets file exists") + } + if runtime.SecretsProviders.Onepassword != nil { + t.Error("Expected OnePassword provider to not be loaded when no vaults configured") + } + }) +} + func TestRuntime_InstallHook(t *testing.T) { t.Run("InstallsHookSuccessfully", func(t *testing.T) { // Given a runtime with loaded shell diff --git a/pkg/runtime/shims.go b/pkg/runtime/shims.go new file mode 100644 index 000000000..99bcd1c13 --- /dev/null +++ b/pkg/runtime/shims.go @@ -0,0 +1,22 @@ +package runtime + +import "os" + +// Shims provides a testable interface for system operations used by pipelines. +// This struct-based approach allows for better isolation during testing by enabling +// dependency injection of mock implementations for file system and environment operations. +// Each pipeline can use its own Shims instance with customized behavior for testing scenarios. +type Shims struct { + Stat func(name string) (os.FileInfo, error) + Getenv func(key string) string +} + +// NewShims creates a new Shims instance with default system call implementations. +// The returned instance provides direct access to os package functions and can be +// used in production environments or as a base for creating test-specific variants. +func NewShims() *Shims { + return &Shims{ + Stat: os.Stat, + Getenv: os.Getenv, + } +} From 0c958afb52da2008d80c7c3942616f6175fc35c1 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:07:50 -0400 Subject: [PATCH 3/3] refactor(runtime): Move loaders in to their own files Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- pkg/runtime/runtime.go | 128 ----- pkg/runtime/runtime_loaders.go | 142 +++++ pkg/runtime/runtime_loaders_test.go | 829 ++++++++++++++++++++++++++++ pkg/runtime/runtime_test.go | 805 --------------------------- 4 files changed, 971 insertions(+), 933 deletions(-) create mode 100644 pkg/runtime/runtime_loaders.go create mode 100644 pkg/runtime/runtime_loaders_test.go diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index fba1fed08..c66f5ad7e 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -2,9 +2,7 @@ package runtime import ( "fmt" - "path/filepath" - secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" "github.com/windsorcli/cli/pkg/artifact" "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/cluster" @@ -107,132 +105,6 @@ func (r *Runtime) Do() error { return r.err } -// LoadShell loads the shell dependency, creating a new default shell if none exists. -func (r *Runtime) LoadShell() *Runtime { - if r.err != nil { - return r - } - - if r.Shell == nil { - r.Shell = shell.NewDefaultShell(r.Injector) - r.Injector.Register("shell", r.Shell) - } - return r -} - -// LoadConfigHandler loads and initializes the configuration handler dependency. -func (r *Runtime) LoadConfigHandler() *Runtime { - if r.err != nil { - return r - } - if r.Shell == nil { - r.err = fmt.Errorf("shell not loaded - call LoadShell() first") - return r - } - - if r.ConfigHandler == nil { - r.ConfigHandler = config.NewConfigHandler(r.Injector) - if err := r.ConfigHandler.Initialize(); err != nil { - r.err = fmt.Errorf("failed to initialize config handler: %w", err) - return r - } - } - return r -} - -// LoadEnvPrinters loads and initializes the environment printers. -func (r *Runtime) LoadEnvPrinters() *Runtime { - if r.err != nil { - return r - } - if r.ConfigHandler == nil { - r.err = fmt.Errorf("config handler not loaded - call LoadConfigHandler() first") - return r - } - if r.EnvPrinters.AwsEnv == nil && r.ConfigHandler.GetBool("aws.enabled", false) { - r.EnvPrinters.AwsEnv = env.NewAwsEnvPrinter(r.Injector) - r.Injector.Register("awsEnv", r.EnvPrinters.AwsEnv) - } - if r.EnvPrinters.AzureEnv == nil && r.ConfigHandler.GetBool("azure.enabled", false) { - r.EnvPrinters.AzureEnv = env.NewAzureEnvPrinter(r.Injector) - r.Injector.Register("azureEnv", r.EnvPrinters.AzureEnv) - } - if r.EnvPrinters.DockerEnv == nil && r.ConfigHandler.GetBool("docker.enabled", false) { - r.EnvPrinters.DockerEnv = env.NewDockerEnvPrinter(r.Injector) - r.Injector.Register("dockerEnv", r.EnvPrinters.DockerEnv) - } - if r.EnvPrinters.KubeEnv == nil && r.ConfigHandler.GetBool("cluster.enabled", false) { - r.EnvPrinters.KubeEnv = env.NewKubeEnvPrinter(r.Injector) - r.Injector.Register("kubeEnv", r.EnvPrinters.KubeEnv) - } - if r.EnvPrinters.TalosEnv == nil && - (r.ConfigHandler.GetString("cluster.driver", "") == "talos" || - r.ConfigHandler.GetString("cluster.driver", "") == "omni") { - r.EnvPrinters.TalosEnv = env.NewTalosEnvPrinter(r.Injector) - r.Injector.Register("talosEnv", r.EnvPrinters.TalosEnv) - } - if r.EnvPrinters.TerraformEnv == nil && r.ConfigHandler.GetBool("terraform.enabled", false) { - r.EnvPrinters.TerraformEnv = env.NewTerraformEnvPrinter(r.Injector) - r.Injector.Register("terraformEnv", r.EnvPrinters.TerraformEnv) - } - if r.EnvPrinters.WindsorEnv == nil { - r.EnvPrinters.WindsorEnv = env.NewWindsorEnvPrinter(r.Injector) - r.Injector.Register("windsorEnv", r.EnvPrinters.WindsorEnv) - } - return r -} - -// LoadSecretsProviders loads and initializes the secrets providers using configuration and environment. -// It detects SOPS and 1Password vaults as in BasePipeline.withSecretsProviders. -func (r *Runtime) LoadSecretsProviders() *Runtime { - if r.err != nil { - return r - } - if r.ConfigHandler == nil { - r.err = fmt.Errorf("config handler not loaded - call LoadConfigHandler() first") - return r - } - - configRoot, err := r.ConfigHandler.GetConfigRoot() - if err != nil { - r.err = fmt.Errorf("error getting config root: %w", err) - return r - } - - secretsFilePaths := []string{"secrets.enc.yaml", "secrets.enc.yml"} - for _, filePath := range secretsFilePaths { - if _, err := r.Shims.Stat(filepath.Join(configRoot, filePath)); err == nil { - if r.SecretsProviders.Sops == nil { - r.SecretsProviders.Sops = secrets.NewSopsSecretsProvider(configRoot, r.Injector) - r.Injector.Register("sopsSecretsProvider", r.SecretsProviders.Sops) - } - break - } - } - - vaults, ok := r.ConfigHandler.Get("secrets.onepassword.vaults").(map[string]secretsConfigType.OnePasswordVault) - if ok && len(vaults) > 0 { - useSDK := r.Shims.Getenv("OP_SERVICE_ACCOUNT_TOKEN") != "" - - for key, vault := range vaults { - vaultCopy := vault - vaultCopy.ID = key - - if r.SecretsProviders.Onepassword == nil { - if useSDK { - r.SecretsProviders.Onepassword = secrets.NewOnePasswordSDKSecretsProvider(vaultCopy, r.Injector) - } else { - r.SecretsProviders.Onepassword = secrets.NewOnePasswordCLISecretsProvider(vaultCopy, r.Injector) - } - r.Injector.Register("onePasswordSecretsProvider", r.SecretsProviders.Onepassword) - break - } - } - } - - return r -} - // InstallHook installs a shell hook for the specified shell type. func (r *Runtime) InstallHook(shellType string) *Runtime { if r.err != nil { diff --git a/pkg/runtime/runtime_loaders.go b/pkg/runtime/runtime_loaders.go new file mode 100644 index 000000000..da7cc37ea --- /dev/null +++ b/pkg/runtime/runtime_loaders.go @@ -0,0 +1,142 @@ +package runtime + +import ( + "fmt" + "path/filepath" + + secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" + "github.com/windsorcli/cli/pkg/config" + "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/secrets" + "github.com/windsorcli/cli/pkg/shell" +) + +// ============================================================================= +// Loader Methods +// ============================================================================= + +// LoadShell loads the shell dependency, creating a new default shell if none exists. +func (r *Runtime) LoadShell() *Runtime { + if r.err != nil { + return r + } + + if r.Shell == nil { + r.Shell = shell.NewDefaultShell(r.Injector) + r.Injector.Register("shell", r.Shell) + } + return r +} + +// LoadConfigHandler loads and initializes the configuration handler dependency. +func (r *Runtime) LoadConfigHandler() *Runtime { + if r.err != nil { + return r + } + if r.Shell == nil { + r.err = fmt.Errorf("shell not loaded - call LoadShell() first") + return r + } + + if r.ConfigHandler == nil { + r.ConfigHandler = config.NewConfigHandler(r.Injector) + if err := r.ConfigHandler.Initialize(); err != nil { + r.err = fmt.Errorf("failed to initialize config handler: %w", err) + return r + } + } + return r +} + +// LoadEnvPrinters loads and initializes the environment printers. +func (r *Runtime) LoadEnvPrinters() *Runtime { + if r.err != nil { + return r + } + if r.ConfigHandler == nil { + r.err = fmt.Errorf("config handler not loaded - call LoadConfigHandler() first") + return r + } + if r.EnvPrinters.AwsEnv == nil && r.ConfigHandler.GetBool("aws.enabled", false) { + r.EnvPrinters.AwsEnv = env.NewAwsEnvPrinter(r.Injector) + r.Injector.Register("awsEnv", r.EnvPrinters.AwsEnv) + } + if r.EnvPrinters.AzureEnv == nil && r.ConfigHandler.GetBool("azure.enabled", false) { + r.EnvPrinters.AzureEnv = env.NewAzureEnvPrinter(r.Injector) + r.Injector.Register("azureEnv", r.EnvPrinters.AzureEnv) + } + if r.EnvPrinters.DockerEnv == nil && r.ConfigHandler.GetBool("docker.enabled", false) { + r.EnvPrinters.DockerEnv = env.NewDockerEnvPrinter(r.Injector) + r.Injector.Register("dockerEnv", r.EnvPrinters.DockerEnv) + } + if r.EnvPrinters.KubeEnv == nil && r.ConfigHandler.GetBool("cluster.enabled", false) { + r.EnvPrinters.KubeEnv = env.NewKubeEnvPrinter(r.Injector) + r.Injector.Register("kubeEnv", r.EnvPrinters.KubeEnv) + } + if r.EnvPrinters.TalosEnv == nil && + (r.ConfigHandler.GetString("cluster.driver", "") == "talos" || + r.ConfigHandler.GetString("cluster.driver", "") == "omni") { + r.EnvPrinters.TalosEnv = env.NewTalosEnvPrinter(r.Injector) + r.Injector.Register("talosEnv", r.EnvPrinters.TalosEnv) + } + if r.EnvPrinters.TerraformEnv == nil && r.ConfigHandler.GetBool("terraform.enabled", false) { + r.EnvPrinters.TerraformEnv = env.NewTerraformEnvPrinter(r.Injector) + r.Injector.Register("terraformEnv", r.EnvPrinters.TerraformEnv) + } + if r.EnvPrinters.WindsorEnv == nil { + r.EnvPrinters.WindsorEnv = env.NewWindsorEnvPrinter(r.Injector) + r.Injector.Register("windsorEnv", r.EnvPrinters.WindsorEnv) + } + return r +} + +// LoadSecretsProviders loads and initializes the secrets providers using configuration and environment. +// It detects SOPS and 1Password vaults as in BasePipeline.withSecretsProviders. +func (r *Runtime) LoadSecretsProviders() *Runtime { + if r.err != nil { + return r + } + if r.ConfigHandler == nil { + r.err = fmt.Errorf("config handler not loaded - call LoadConfigHandler() first") + return r + } + + configRoot, err := r.ConfigHandler.GetConfigRoot() + if err != nil { + r.err = fmt.Errorf("error getting config root: %w", err) + return r + } + + secretsFilePaths := []string{"secrets.enc.yaml", "secrets.enc.yml"} + for _, filePath := range secretsFilePaths { + if _, err := r.Shims.Stat(filepath.Join(configRoot, filePath)); err == nil { + if r.SecretsProviders.Sops == nil { + r.SecretsProviders.Sops = secrets.NewSopsSecretsProvider(configRoot, r.Injector) + r.Injector.Register("sopsSecretsProvider", r.SecretsProviders.Sops) + } + break + } + } + + vaults, ok := r.ConfigHandler.Get("secrets.onepassword.vaults").(map[string]secretsConfigType.OnePasswordVault) + if ok && len(vaults) > 0 { + useSDK := r.Shims.Getenv("OP_SERVICE_ACCOUNT_TOKEN") != "" + + for key, vault := range vaults { + vaultCopy := vault + vaultCopy.ID = key + + if r.SecretsProviders.Onepassword == nil { + if useSDK { + r.SecretsProviders.Onepassword = secrets.NewOnePasswordSDKSecretsProvider(vaultCopy, r.Injector) + } else { + r.SecretsProviders.Onepassword = secrets.NewOnePasswordCLISecretsProvider(vaultCopy, r.Injector) + } + r.Injector.Register("onePasswordSecretsProvider", r.SecretsProviders.Onepassword) + break + } + } + } + + return r +} diff --git a/pkg/runtime/runtime_loaders_test.go b/pkg/runtime/runtime_loaders_test.go new file mode 100644 index 000000000..4e1319dde --- /dev/null +++ b/pkg/runtime/runtime_loaders_test.go @@ -0,0 +1,829 @@ +package runtime + +import ( + "errors" + "os" + "strings" + "testing" + + secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" + "github.com/windsorcli/cli/pkg/config" + "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/shell" +) + +// The RuntimeLoadersTest is a test suite for the Runtime loader methods. +// It provides comprehensive test coverage for dependency loading, error propagation, +// and method chaining in the Windsor CLI runtime system. +// The RuntimeLoadersTest acts as a validation framework for loader functionality, +// ensuring reliable dependency management, proper error handling, and method chaining. + +// ============================================================================= +// Test Setup +// ============================================================================= + +// setupMocks creates a new set of mocks for testing +func setupMocks(t *testing.T) *Dependencies { + t.Helper() + + return &Dependencies{ + Injector: di.NewInjector(), + Shell: shell.NewMockShell(), + ConfigHandler: config.NewMockConfigHandler(), + } +} + +// ============================================================================= +// Test Loader Methods +// ============================================================================= + +func TestRuntime_LoadShell(t *testing.T) { + t.Run("LoadsShellSuccessfully", func(t *testing.T) { + // Given a runtime with dependencies + mocks := setupMocks(t) + runtime := NewRuntime(mocks) + + // When loading shell + result := runtime.LoadShell() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadShell to return the same runtime instance") + } + + // And shell should be loaded + if runtime.Shell == nil { + t.Error("Expected shell to be loaded") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + }) + + t.Run("CreatesNewShellWhenNoneExists", func(t *testing.T) { + // Given a runtime without pre-loaded shell + runtime := NewRuntime() + + // When loading shell + result := runtime.LoadShell() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadShell to return the same runtime instance") + } + + // And shell should be loaded + if runtime.Shell == nil { + t.Error("Expected shell to be loaded") + } + + // And shell should be registered in injector + resolvedShell := runtime.Injector.Resolve("shell") + if resolvedShell == nil { + t.Error("Expected shell to be registered in injector") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + }) + + t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { + // Given a runtime with an existing error (no pre-loaded dependencies) + runtime := NewRuntime() + runtime.err = errors.New("existing error") + + // When loading shell + result := runtime.LoadShell() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadShell to return the same runtime instance") + } + + // And shell should not be loaded + if runtime.Shell != nil { + t.Error("Expected shell to not be loaded when error exists") + } + + // And original error should be preserved + if runtime.err.Error() != "existing error" { + t.Errorf("Expected original error to be preserved, got %v", runtime.err) + } + }) +} + +func TestRuntime_LoadConfigHandler(t *testing.T) { + t.Run("LoadsConfigHandlerSuccessfully", func(t *testing.T) { + // Given a runtime with loaded shell + mocks := setupMocks(t) + runtime := NewRuntime(mocks).LoadShell() + + // When loading config handler + result := runtime.LoadConfigHandler() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadConfigHandler to return the same runtime instance") + } + + // And config handler should be loaded + if runtime.ConfigHandler == nil { + t.Error("Expected config handler to be loaded") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + }) + + t.Run("ReturnsErrorWhenShellNotLoaded", func(t *testing.T) { + // Given a runtime without loaded shell (no pre-loaded dependencies) + runtime := NewRuntime() + + // When loading config handler + result := runtime.LoadConfigHandler() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadConfigHandler to return the same runtime instance") + } + + // And error should be set + if runtime.err == nil { + t.Error("Expected error when shell not loaded") + } + + expectedError := "shell not loaded - call LoadShell() first" + if runtime.err.Error() != expectedError { + t.Errorf("Expected error %q, got %q", expectedError, runtime.err.Error()) + } + }) + + t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { + // Given a runtime with an existing error (no pre-loaded dependencies) + runtime := NewRuntime() + runtime.err = errors.New("existing error") + + // When loading config handler + result := runtime.LoadConfigHandler() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadConfigHandler to return the same runtime instance") + } + + // And config handler should not be loaded + if runtime.ConfigHandler != nil { + t.Error("Expected config handler to not be loaded when error exists") + } + + // And original error should be preserved + if runtime.err.Error() != "existing error" { + t.Errorf("Expected original error to be preserved, got %v", runtime.err) + } + }) + + t.Run("PropagatesConfigHandlerInitializationError", func(t *testing.T) { + // Given a runtime with an injector that cannot resolve the shell + runtime := NewRuntime() + runtime.Shell = shell.NewMockShell() + // Don't register the shell in the injector - this will cause initialization to fail + + // When loading config handler + result := runtime.LoadConfigHandler() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadConfigHandler to return the same runtime instance") + } + + // And error should be set from initialization failure + if runtime.err == nil { + t.Error("Expected error from config handler initialization failure") + } else { + expectedError := "failed to initialize config handler" + if !strings.Contains(runtime.err.Error(), expectedError) { + t.Errorf("Expected error to contain %q, got %q", expectedError, runtime.err.Error()) + } + } + }) +} + +func TestRuntime_LoadEnvPrinters(t *testing.T) { + t.Run("LoadsEnvPrintersSuccessfully", func(t *testing.T) { + // Given a runtime with loaded shell and config handler + mocks := setupMocks(t) + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And WindsorEnv should always be loaded + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + + // And WindsorEnv should be registered in injector + resolvedWindsorEnv := runtime.Injector.Resolve("windsorEnv") + if resolvedWindsorEnv == nil { + t.Error("Expected WindsorEnv to be registered in injector") + } + }) + + t.Run("ReturnsErrorWhenConfigHandlerNotLoaded", func(t *testing.T) { + // Given a runtime without loaded config handler (no pre-loaded dependencies) + runtime := NewRuntime() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And error should be set + if runtime.err == nil { + t.Error("Expected error when config handler not loaded") + } + + expectedError := "config handler not loaded - call LoadConfigHandler() first" + if runtime.err.Error() != expectedError { + t.Errorf("Expected error %q, got %q", expectedError, runtime.err.Error()) + } + }) + + t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { + // Given a runtime with an existing error (no pre-loaded dependencies) + runtime := NewRuntime() + runtime.err = errors.New("existing error") + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And original error should be preserved + if runtime.err.Error() != "existing error" { + t.Errorf("Expected original error to be preserved, got %v", runtime.err) + } + + // And no env printers should be loaded + if runtime.EnvPrinters.WindsorEnv != nil { + t.Error("Expected no env printers to be loaded when error exists") + } + }) + + t.Run("LoadsOnlyEnabledEnvPrinters", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with specific enabled features + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetBoolFunc = func(key string, defaultValue ...bool) bool { + switch key { + case "aws.enabled": + return true + case "azure.enabled": + return false + case "docker.enabled": + return true + case "cluster.enabled": + return false + case "terraform.enabled": + return true + default: + if len(defaultValue) > 0 { + return defaultValue[0] + } + return false + } + } + mocks.ConfigHandler.(*config.MockConfigHandler).GetStringFunc = func(key string, defaultValue ...string) string { + if key == "cluster.driver" { + return "kubernetes" + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return "" + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And enabled env printers should be loaded + if runtime.EnvPrinters.AwsEnv == nil { + t.Error("Expected AwsEnv to be loaded when enabled") + } + if runtime.EnvPrinters.DockerEnv == nil { + t.Error("Expected DockerEnv to be loaded when enabled") + } + if runtime.EnvPrinters.TerraformEnv == nil { + t.Error("Expected TerraformEnv to be loaded when enabled") + } + + // And disabled env printers should not be loaded + if runtime.EnvPrinters.AzureEnv != nil { + t.Error("Expected AzureEnv to not be loaded when disabled") + } + if runtime.EnvPrinters.KubeEnv != nil { + t.Error("Expected KubeEnv to not be loaded when disabled") + } + if runtime.EnvPrinters.TalosEnv != nil { + t.Error("Expected TalosEnv to not be loaded when cluster driver is not talos/omni") + } + + // And WindsorEnv should always be loaded + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + }) + + t.Run("LoadsWindsorEnvPrinterAlways", func(t *testing.T) { + // Given a runtime with loaded shell and config handler + mocks := setupMocks(t) + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And WindsorEnv should always be loaded regardless of config + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + + // And WindsorEnv should be registered in injector + resolvedWindsorEnv := runtime.Injector.Resolve("windsorEnv") + if resolvedWindsorEnv == nil { + t.Error("Expected WindsorEnv to be registered in injector") + } + }) + + t.Run("LoadsTalosEnvPrinterForTalosDriver", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with talos driver + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetStringFunc = func(key string, defaultValue ...string) string { + if key == "cluster.driver" { + return "talos" + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return "" + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And TalosEnv should be loaded for talos driver + if runtime.EnvPrinters.TalosEnv == nil { + t.Error("Expected TalosEnv to be loaded for talos driver") + } + + // And TalosEnv should be registered in injector + resolvedTalosEnv := runtime.Injector.Resolve("talosEnv") + if resolvedTalosEnv == nil { + t.Error("Expected TalosEnv to be registered in injector") + } + }) + + t.Run("LoadsTalosEnvPrinterForOmniDriver", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with omni driver + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetStringFunc = func(key string, defaultValue ...string) string { + if key == "cluster.driver" { + return "omni" + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return "" + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And TalosEnv should be loaded for omni driver + if runtime.EnvPrinters.TalosEnv == nil { + t.Error("Expected TalosEnv to be loaded for omni driver") + } + + // And TalosEnv should be registered in injector + resolvedTalosEnv := runtime.Injector.Resolve("talosEnv") + if resolvedTalosEnv == nil { + t.Error("Expected TalosEnv to be registered in injector") + } + }) + + t.Run("LoadsAzureEnvPrinterWhenEnabled", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with azure enabled + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "azure.enabled" { + return true + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return false + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And AzureEnv should be loaded when enabled + if runtime.EnvPrinters.AzureEnv == nil { + t.Error("Expected AzureEnv to be loaded when enabled") + } + + // And AzureEnv should be registered in injector + resolvedAzureEnv := runtime.Injector.Resolve("azureEnv") + if resolvedAzureEnv == nil { + t.Error("Expected AzureEnv to be registered in injector") + } + + // And WindsorEnv should always be loaded + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + }) + + t.Run("LoadsKubeEnvPrinterWhenEnabled", func(t *testing.T) { + // Given a runtime with loaded shell and config handler with cluster enabled + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "cluster.enabled" { + return true + } + if len(defaultValue) > 0 { + return defaultValue[0] + } + return false + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading env printers + result := runtime.LoadEnvPrinters() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadEnvPrinters to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And KubeEnv should be loaded when cluster is enabled + if runtime.EnvPrinters.KubeEnv == nil { + t.Error("Expected KubeEnv to be loaded when cluster is enabled") + } + + // And KubeEnv should be registered in injector + resolvedKubeEnv := runtime.Injector.Resolve("kubeEnv") + if resolvedKubeEnv == nil { + t.Error("Expected KubeEnv to be registered in injector") + } + + // And WindsorEnv should always be loaded + if runtime.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv to be loaded") + } + }) +} + +func TestRuntime_LoadSecretsProviders(t *testing.T) { + t.Run("LoadsSecretsProvidersSuccessfully", func(t *testing.T) { + // Given a runtime with loaded shell and config handler + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + }) + + t.Run("ReturnsErrorWhenConfigHandlerNotLoaded", func(t *testing.T) { + // Given a runtime without loaded config handler (no pre-loaded dependencies) + runtime := NewRuntime() + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And error should be set + if runtime.err == nil { + t.Error("Expected error when config handler not loaded") + } + + expectedError := "config handler not loaded - call LoadConfigHandler() first" + if runtime.err.Error() != expectedError { + t.Errorf("Expected error %q, got %q", expectedError, runtime.err.Error()) + } + }) + + t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { + // Given a runtime with an existing error (no pre-loaded dependencies) + runtime := NewRuntime() + runtime.err = errors.New("existing error") + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And original error should be preserved + if runtime.err.Error() != "existing error" { + t.Errorf("Expected original error to be preserved, got %v", runtime.err) + } + + // And no secrets providers should be loaded + if runtime.SecretsProviders.Sops != nil { + t.Error("Expected no secrets providers to be loaded when error exists") + } + if runtime.SecretsProviders.Onepassword != nil { + t.Error("Expected no secrets providers to be loaded when error exists") + } + }) + + t.Run("PropagatesConfigRootError", func(t *testing.T) { + // Given a runtime with loaded shell and config handler that returns error for GetConfigRoot + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "", errors.New("config root error") + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And error should be propagated + if runtime.err == nil { + t.Error("Expected error to be propagated from config root") + } else { + expectedError := "error getting config root" + if !strings.Contains(runtime.err.Error(), expectedError) { + t.Errorf("Expected error to contain %q, got %q", expectedError, runtime.err.Error()) + } + } + }) + + t.Run("LoadsSopsProviderWhenSecretsFileExists", func(t *testing.T) { + // Given a runtime with loaded shell and config handler, and secrets file exists + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // Mock Stat to return success for secrets.enc.yaml + runtime.Shims.Stat = func(name string) (os.FileInfo, error) { + if strings.Contains(name, "secrets.enc.yaml") { + return nil, nil // Success - file exists + } + return nil, errors.New("file not found") + } + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And Sops provider should be loaded + if runtime.SecretsProviders.Sops == nil { + t.Error("Expected Sops provider to be loaded when secrets file exists") + } + + // And Sops provider should be registered in injector + resolvedSops := runtime.Injector.Resolve("sopsSecretsProvider") + if resolvedSops == nil { + t.Error("Expected Sops provider to be registered in injector") + } + }) + + t.Run("LoadsOnePasswordSDKProviderWhenTokenExists", func(t *testing.T) { + // Given a runtime with loaded shell and config handler, OnePassword vaults configured, and SDK token + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + mocks.ConfigHandler.(*config.MockConfigHandler).GetFunc = func(key string) any { + if key == "secrets.onepassword.vaults" { + return map[string]secretsConfigType.OnePasswordVault{ + "vault1": {URL: "https://vault1.com", Name: "Vault 1"}, + } + } + return nil + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // Mock Getenv to return SDK token + runtime.Shims.Getenv = func(key string) string { + if key == "OP_SERVICE_ACCOUNT_TOKEN" { + return "test-token" + } + return "" + } + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And OnePassword provider should be loaded + if runtime.SecretsProviders.Onepassword == nil { + t.Error("Expected OnePassword provider to be loaded when vaults configured and SDK token exists") + } + + // And OnePassword provider should be registered in injector + resolvedOnePassword := runtime.Injector.Resolve("onePasswordSecretsProvider") + if resolvedOnePassword == nil { + t.Error("Expected OnePassword provider to be registered in injector") + } + }) + + t.Run("LoadsOnePasswordCLIProviderWhenNoToken", func(t *testing.T) { + // Given a runtime with loaded shell and config handler, OnePassword vaults configured, but no SDK token + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + mocks.ConfigHandler.(*config.MockConfigHandler).GetFunc = func(key string) any { + if key == "secrets.onepassword.vaults" { + return map[string]secretsConfigType.OnePasswordVault{ + "vault1": {URL: "https://vault1.com", Name: "Vault 1"}, + } + } + return nil + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // Mock Getenv to return no SDK token + runtime.Shims.Getenv = func(key string) string { + return "" + } + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And OnePassword provider should be loaded + if runtime.SecretsProviders.Onepassword == nil { + t.Error("Expected OnePassword provider to be loaded when vaults configured but no SDK token") + } + + // And OnePassword provider should be registered in injector + resolvedOnePassword := runtime.Injector.Resolve("onePasswordSecretsProvider") + if resolvedOnePassword == nil { + t.Error("Expected OnePassword provider to be registered in injector") + } + }) + + t.Run("DoesNotLoadProvidersWhenNoConfig", func(t *testing.T) { + // Given a runtime with loaded shell and config handler, but no secrets configuration + mocks := setupMocks(t) + mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { + return "/test/config/root", nil + } + mocks.ConfigHandler.(*config.MockConfigHandler).GetFunc = func(key string) any { + return nil // No secrets configuration + } + runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() + + // Mock Stat to return file not found for secrets files + runtime.Shims.Stat = func(name string) (os.FileInfo, error) { + return nil, errors.New("file not found") + } + + // Mock Getenv to return no SDK token + runtime.Shims.Getenv = func(key string) string { + return "" + } + + // When loading secrets providers + result := runtime.LoadSecretsProviders() + + // Then should return the same runtime instance + if result != runtime { + t.Error("Expected LoadSecretsProviders to return the same runtime instance") + } + + // And no error should be set + if runtime.err != nil { + t.Errorf("Expected no error, got %v", runtime.err) + } + + // And no secrets providers should be loaded + if runtime.SecretsProviders.Sops != nil { + t.Error("Expected Sops provider to not be loaded when no secrets file exists") + } + if runtime.SecretsProviders.Onepassword != nil { + t.Error("Expected OnePassword provider to not be loaded when no vaults configured") + } + }) +} diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index c4ecda282..e29e62e57 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -2,13 +2,10 @@ package runtime import ( "errors" - "os" "strings" "testing" - secretsConfigType "github.com/windsorcli/cli/api/v1alpha1/secrets" "github.com/windsorcli/cli/pkg/config" - "github.com/windsorcli/cli/pkg/di" "github.com/windsorcli/cli/pkg/shell" ) @@ -22,17 +19,6 @@ import ( // Test Setup // ============================================================================= -// setupMocks creates a new set of mocks for testing -func setupMocks(t *testing.T) *Dependencies { - t.Helper() - - return &Dependencies{ - Injector: di.NewInjector(), - Shell: shell.NewMockShell(), - ConfigHandler: config.NewMockConfigHandler(), - } -} - // ============================================================================= // Test Public Methods // ============================================================================= @@ -56,797 +42,6 @@ func TestRuntime_NewRuntime(t *testing.T) { }) } -func TestRuntime_LoadShell(t *testing.T) { - t.Run("LoadsShellSuccessfully", func(t *testing.T) { - // Given a runtime with dependencies - mocks := setupMocks(t) - runtime := NewRuntime(mocks) - - // When loading shell - result := runtime.LoadShell() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadShell to return the same runtime instance") - } - - // And shell should be loaded - if runtime.Shell == nil { - t.Error("Expected shell to be loaded") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - }) - - t.Run("CreatesNewShellWhenNoneExists", func(t *testing.T) { - // Given a runtime without pre-loaded shell - runtime := NewRuntime() - - // When loading shell - result := runtime.LoadShell() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadShell to return the same runtime instance") - } - - // And shell should be loaded - if runtime.Shell == nil { - t.Error("Expected shell to be loaded") - } - - // And shell should be registered in injector - resolvedShell := runtime.Injector.Resolve("shell") - if resolvedShell == nil { - t.Error("Expected shell to be registered in injector") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - }) - - t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { - // Given a runtime with an existing error (no pre-loaded dependencies) - runtime := NewRuntime() - runtime.err = errors.New("existing error") - - // When loading shell - result := runtime.LoadShell() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadShell to return the same runtime instance") - } - - // And shell should not be loaded - if runtime.Shell != nil { - t.Error("Expected shell to not be loaded when error exists") - } - - // And original error should be preserved - if runtime.err.Error() != "existing error" { - t.Errorf("Expected original error to be preserved, got %v", runtime.err) - } - }) -} - -func TestRuntime_LoadConfigHandler(t *testing.T) { - t.Run("LoadsConfigHandlerSuccessfully", func(t *testing.T) { - // Given a runtime with loaded shell - mocks := setupMocks(t) - runtime := NewRuntime(mocks).LoadShell() - - // When loading config handler - result := runtime.LoadConfigHandler() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadConfigHandler to return the same runtime instance") - } - - // And config handler should be loaded - if runtime.ConfigHandler == nil { - t.Error("Expected config handler to be loaded") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - }) - - t.Run("ReturnsErrorWhenShellNotLoaded", func(t *testing.T) { - // Given a runtime without loaded shell (no pre-loaded dependencies) - runtime := NewRuntime() - - // When loading config handler - result := runtime.LoadConfigHandler() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadConfigHandler to return the same runtime instance") - } - - // And error should be set - if runtime.err == nil { - t.Error("Expected error when shell not loaded") - } - - expectedError := "shell not loaded - call LoadShell() first" - if runtime.err.Error() != expectedError { - t.Errorf("Expected error %q, got %q", expectedError, runtime.err.Error()) - } - }) - - t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { - // Given a runtime with an existing error (no pre-loaded dependencies) - runtime := NewRuntime() - runtime.err = errors.New("existing error") - - // When loading config handler - result := runtime.LoadConfigHandler() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadConfigHandler to return the same runtime instance") - } - - // And config handler should not be loaded - if runtime.ConfigHandler != nil { - t.Error("Expected config handler to not be loaded when error exists") - } - - // And original error should be preserved - if runtime.err.Error() != "existing error" { - t.Errorf("Expected original error to be preserved, got %v", runtime.err) - } - }) - - t.Run("PropagatesConfigHandlerInitializationError", func(t *testing.T) { - // Given a runtime with an injector that cannot resolve the shell - runtime := NewRuntime() - runtime.Shell = shell.NewMockShell() - // Don't register the shell in the injector - this will cause initialization to fail - - // When loading config handler - result := runtime.LoadConfigHandler() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadConfigHandler to return the same runtime instance") - } - - // And error should be set from initialization failure - if runtime.err == nil { - t.Error("Expected error from config handler initialization failure") - } else { - expectedError := "failed to initialize config handler" - if !strings.Contains(runtime.err.Error(), expectedError) { - t.Errorf("Expected error to contain %q, got %q", expectedError, runtime.err.Error()) - } - } - }) -} - -func TestRuntime_LoadEnvPrinters(t *testing.T) { - t.Run("LoadsEnvPrintersSuccessfully", func(t *testing.T) { - // Given a runtime with loaded shell and config handler - mocks := setupMocks(t) - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // When loading env printers - result := runtime.LoadEnvPrinters() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadEnvPrinters to return the same runtime instance") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - - // And WindsorEnv should always be loaded - if runtime.EnvPrinters.WindsorEnv == nil { - t.Error("Expected WindsorEnv to be loaded") - } - - // And WindsorEnv should be registered in injector - resolvedWindsorEnv := runtime.Injector.Resolve("windsorEnv") - if resolvedWindsorEnv == nil { - t.Error("Expected WindsorEnv to be registered in injector") - } - }) - - t.Run("ReturnsErrorWhenConfigHandlerNotLoaded", func(t *testing.T) { - // Given a runtime without loaded config handler (no pre-loaded dependencies) - runtime := NewRuntime() - - // When loading env printers - result := runtime.LoadEnvPrinters() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadEnvPrinters to return the same runtime instance") - } - - // And error should be set - if runtime.err == nil { - t.Error("Expected error when config handler not loaded") - } - - expectedError := "config handler not loaded - call LoadConfigHandler() first" - if runtime.err.Error() != expectedError { - t.Errorf("Expected error %q, got %q", expectedError, runtime.err.Error()) - } - }) - - t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { - // Given a runtime with an existing error (no pre-loaded dependencies) - runtime := NewRuntime() - runtime.err = errors.New("existing error") - - // When loading env printers - result := runtime.LoadEnvPrinters() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadEnvPrinters to return the same runtime instance") - } - - // And original error should be preserved - if runtime.err.Error() != "existing error" { - t.Errorf("Expected original error to be preserved, got %v", runtime.err) - } - - // And no env printers should be loaded - if runtime.EnvPrinters.WindsorEnv != nil { - t.Error("Expected no env printers to be loaded when error exists") - } - }) - - t.Run("LoadsOnlyEnabledEnvPrinters", func(t *testing.T) { - // Given a runtime with loaded shell and config handler with specific enabled features - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetBoolFunc = func(key string, defaultValue ...bool) bool { - switch key { - case "aws.enabled": - return true - case "azure.enabled": - return false - case "docker.enabled": - return true - case "cluster.enabled": - return false - case "terraform.enabled": - return true - default: - if len(defaultValue) > 0 { - return defaultValue[0] - } - return false - } - } - mocks.ConfigHandler.(*config.MockConfigHandler).GetStringFunc = func(key string, defaultValue ...string) string { - if key == "cluster.driver" { - return "kubernetes" - } - if len(defaultValue) > 0 { - return defaultValue[0] - } - return "" - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // When loading env printers - result := runtime.LoadEnvPrinters() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadEnvPrinters to return the same runtime instance") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - - // And enabled env printers should be loaded - if runtime.EnvPrinters.AwsEnv == nil { - t.Error("Expected AwsEnv to be loaded when enabled") - } - if runtime.EnvPrinters.DockerEnv == nil { - t.Error("Expected DockerEnv to be loaded when enabled") - } - if runtime.EnvPrinters.TerraformEnv == nil { - t.Error("Expected TerraformEnv to be loaded when enabled") - } - - // And disabled env printers should not be loaded - if runtime.EnvPrinters.AzureEnv != nil { - t.Error("Expected AzureEnv to not be loaded when disabled") - } - if runtime.EnvPrinters.KubeEnv != nil { - t.Error("Expected KubeEnv to not be loaded when disabled") - } - if runtime.EnvPrinters.TalosEnv != nil { - t.Error("Expected TalosEnv to not be loaded when cluster driver is not talos/omni") - } - - // And WindsorEnv should always be loaded - if runtime.EnvPrinters.WindsorEnv == nil { - t.Error("Expected WindsorEnv to be loaded") - } - }) - - t.Run("LoadsWindsorEnvPrinterAlways", func(t *testing.T) { - // Given a runtime with loaded shell and config handler - mocks := setupMocks(t) - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // When loading env printers - result := runtime.LoadEnvPrinters() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadEnvPrinters to return the same runtime instance") - } - - // And WindsorEnv should always be loaded regardless of config - if runtime.EnvPrinters.WindsorEnv == nil { - t.Error("Expected WindsorEnv to be loaded") - } - - // And WindsorEnv should be registered in injector - resolvedWindsorEnv := runtime.Injector.Resolve("windsorEnv") - if resolvedWindsorEnv == nil { - t.Error("Expected WindsorEnv to be registered in injector") - } - }) - - t.Run("LoadsTalosEnvPrinterForTalosDriver", func(t *testing.T) { - // Given a runtime with loaded shell and config handler with talos driver - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetStringFunc = func(key string, defaultValue ...string) string { - if key == "cluster.driver" { - return "talos" - } - if len(defaultValue) > 0 { - return defaultValue[0] - } - return "" - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // When loading env printers - result := runtime.LoadEnvPrinters() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadEnvPrinters to return the same runtime instance") - } - - // And TalosEnv should be loaded for talos driver - if runtime.EnvPrinters.TalosEnv == nil { - t.Error("Expected TalosEnv to be loaded for talos driver") - } - - // And TalosEnv should be registered in injector - resolvedTalosEnv := runtime.Injector.Resolve("talosEnv") - if resolvedTalosEnv == nil { - t.Error("Expected TalosEnv to be registered in injector") - } - }) - - t.Run("LoadsTalosEnvPrinterForOmniDriver", func(t *testing.T) { - // Given a runtime with loaded shell and config handler with omni driver - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetStringFunc = func(key string, defaultValue ...string) string { - if key == "cluster.driver" { - return "omni" - } - if len(defaultValue) > 0 { - return defaultValue[0] - } - return "" - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // When loading env printers - result := runtime.LoadEnvPrinters() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadEnvPrinters to return the same runtime instance") - } - - // And TalosEnv should be loaded for omni driver - if runtime.EnvPrinters.TalosEnv == nil { - t.Error("Expected TalosEnv to be loaded for omni driver") - } - - // And TalosEnv should be registered in injector - resolvedTalosEnv := runtime.Injector.Resolve("talosEnv") - if resolvedTalosEnv == nil { - t.Error("Expected TalosEnv to be registered in injector") - } - }) - - t.Run("LoadsAzureEnvPrinterWhenEnabled", func(t *testing.T) { - // Given a runtime with loaded shell and config handler with azure enabled - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetBoolFunc = func(key string, defaultValue ...bool) bool { - if key == "azure.enabled" { - return true - } - if len(defaultValue) > 0 { - return defaultValue[0] - } - return false - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // When loading env printers - result := runtime.LoadEnvPrinters() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadEnvPrinters to return the same runtime instance") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - - // And AzureEnv should be loaded when enabled - if runtime.EnvPrinters.AzureEnv == nil { - t.Error("Expected AzureEnv to be loaded when enabled") - } - - // And AzureEnv should be registered in injector - resolvedAzureEnv := runtime.Injector.Resolve("azureEnv") - if resolvedAzureEnv == nil { - t.Error("Expected AzureEnv to be registered in injector") - } - - // And WindsorEnv should always be loaded - if runtime.EnvPrinters.WindsorEnv == nil { - t.Error("Expected WindsorEnv to be loaded") - } - }) - - t.Run("LoadsKubeEnvPrinterWhenEnabled", func(t *testing.T) { - // Given a runtime with loaded shell and config handler with cluster enabled - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetBoolFunc = func(key string, defaultValue ...bool) bool { - if key == "cluster.enabled" { - return true - } - if len(defaultValue) > 0 { - return defaultValue[0] - } - return false - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // When loading env printers - result := runtime.LoadEnvPrinters() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadEnvPrinters to return the same runtime instance") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - - // And KubeEnv should be loaded when cluster is enabled - if runtime.EnvPrinters.KubeEnv == nil { - t.Error("Expected KubeEnv to be loaded when cluster is enabled") - } - - // And KubeEnv should be registered in injector - resolvedKubeEnv := runtime.Injector.Resolve("kubeEnv") - if resolvedKubeEnv == nil { - t.Error("Expected KubeEnv to be registered in injector") - } - - // And WindsorEnv should always be loaded - if runtime.EnvPrinters.WindsorEnv == nil { - t.Error("Expected WindsorEnv to be loaded") - } - }) -} - -func TestRuntime_LoadSecretsProviders(t *testing.T) { - t.Run("LoadsSecretsProvidersSuccessfully", func(t *testing.T) { - // Given a runtime with loaded shell and config handler - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { - return "/test/config/root", nil - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // When loading secrets providers - result := runtime.LoadSecretsProviders() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadSecretsProviders to return the same runtime instance") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - }) - - t.Run("ReturnsErrorWhenConfigHandlerNotLoaded", func(t *testing.T) { - // Given a runtime without loaded config handler (no pre-loaded dependencies) - runtime := NewRuntime() - - // When loading secrets providers - result := runtime.LoadSecretsProviders() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadSecretsProviders to return the same runtime instance") - } - - // And error should be set - if runtime.err == nil { - t.Error("Expected error when config handler not loaded") - } - - expectedError := "config handler not loaded - call LoadConfigHandler() first" - if runtime.err.Error() != expectedError { - t.Errorf("Expected error %q, got %q", expectedError, runtime.err.Error()) - } - }) - - t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { - // Given a runtime with an existing error (no pre-loaded dependencies) - runtime := NewRuntime() - runtime.err = errors.New("existing error") - - // When loading secrets providers - result := runtime.LoadSecretsProviders() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadSecretsProviders to return the same runtime instance") - } - - // And original error should be preserved - if runtime.err.Error() != "existing error" { - t.Errorf("Expected original error to be preserved, got %v", runtime.err) - } - - // And no secrets providers should be loaded - if runtime.SecretsProviders.Sops != nil { - t.Error("Expected no secrets providers to be loaded when error exists") - } - if runtime.SecretsProviders.Onepassword != nil { - t.Error("Expected no secrets providers to be loaded when error exists") - } - }) - - t.Run("PropagatesConfigRootError", func(t *testing.T) { - // Given a runtime with loaded shell and config handler that returns error for GetConfigRoot - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { - return "", errors.New("config root error") - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // When loading secrets providers - result := runtime.LoadSecretsProviders() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadSecretsProviders to return the same runtime instance") - } - - // And error should be propagated - if runtime.err == nil { - t.Error("Expected error to be propagated from config root") - } else { - expectedError := "error getting config root" - if !strings.Contains(runtime.err.Error(), expectedError) { - t.Errorf("Expected error to contain %q, got %q", expectedError, runtime.err.Error()) - } - } - }) - - t.Run("LoadsSopsProviderWhenSecretsFileExists", func(t *testing.T) { - // Given a runtime with loaded shell and config handler, and secrets file exists - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { - return "/test/config/root", nil - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // Mock Stat to return success for secrets.enc.yaml - runtime.Shims.Stat = func(name string) (os.FileInfo, error) { - if strings.Contains(name, "secrets.enc.yaml") { - return nil, nil // Success - file exists - } - return nil, errors.New("file not found") - } - - // When loading secrets providers - result := runtime.LoadSecretsProviders() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadSecretsProviders to return the same runtime instance") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - - // And Sops provider should be loaded - if runtime.SecretsProviders.Sops == nil { - t.Error("Expected Sops provider to be loaded when secrets file exists") - } - - // And Sops provider should be registered in injector - resolvedSops := runtime.Injector.Resolve("sopsSecretsProvider") - if resolvedSops == nil { - t.Error("Expected Sops provider to be registered in injector") - } - }) - - t.Run("LoadsOnePasswordSDKProviderWhenTokenExists", func(t *testing.T) { - // Given a runtime with loaded shell and config handler, OnePassword vaults configured, and SDK token - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { - return "/test/config/root", nil - } - mocks.ConfigHandler.(*config.MockConfigHandler).GetFunc = func(key string) any { - if key == "secrets.onepassword.vaults" { - return map[string]secretsConfigType.OnePasswordVault{ - "vault1": {URL: "https://vault1.com", Name: "Vault 1"}, - } - } - return nil - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // Mock Getenv to return SDK token - runtime.Shims.Getenv = func(key string) string { - if key == "OP_SERVICE_ACCOUNT_TOKEN" { - return "test-token" - } - return "" - } - - // When loading secrets providers - result := runtime.LoadSecretsProviders() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadSecretsProviders to return the same runtime instance") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - - // And OnePassword provider should be loaded - if runtime.SecretsProviders.Onepassword == nil { - t.Error("Expected OnePassword provider to be loaded when vaults configured and SDK token exists") - } - - // And OnePassword provider should be registered in injector - resolvedOnePassword := runtime.Injector.Resolve("onePasswordSecretsProvider") - if resolvedOnePassword == nil { - t.Error("Expected OnePassword provider to be registered in injector") - } - }) - - t.Run("LoadsOnePasswordCLIProviderWhenNoToken", func(t *testing.T) { - // Given a runtime with loaded shell and config handler, OnePassword vaults configured, but no SDK token - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { - return "/test/config/root", nil - } - mocks.ConfigHandler.(*config.MockConfigHandler).GetFunc = func(key string) any { - if key == "secrets.onepassword.vaults" { - return map[string]secretsConfigType.OnePasswordVault{ - "vault1": {URL: "https://vault1.com", Name: "Vault 1"}, - } - } - return nil - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // Mock Getenv to return no SDK token - runtime.Shims.Getenv = func(key string) string { - return "" - } - - // When loading secrets providers - result := runtime.LoadSecretsProviders() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadSecretsProviders to return the same runtime instance") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - - // And OnePassword provider should be loaded - if runtime.SecretsProviders.Onepassword == nil { - t.Error("Expected OnePassword provider to be loaded when vaults configured but no SDK token") - } - - // And OnePassword provider should be registered in injector - resolvedOnePassword := runtime.Injector.Resolve("onePasswordSecretsProvider") - if resolvedOnePassword == nil { - t.Error("Expected OnePassword provider to be registered in injector") - } - }) - - t.Run("DoesNotLoadProvidersWhenNoConfig", func(t *testing.T) { - // Given a runtime with loaded shell and config handler, but no secrets configuration - mocks := setupMocks(t) - mocks.ConfigHandler.(*config.MockConfigHandler).GetConfigRootFunc = func() (string, error) { - return "/test/config/root", nil - } - mocks.ConfigHandler.(*config.MockConfigHandler).GetFunc = func(key string) any { - return nil // No secrets configuration - } - runtime := NewRuntime(mocks).LoadShell().LoadConfigHandler() - - // Mock Stat to return file not found for secrets files - runtime.Shims.Stat = func(name string) (os.FileInfo, error) { - return nil, errors.New("file not found") - } - - // Mock Getenv to return no SDK token - runtime.Shims.Getenv = func(key string) string { - return "" - } - - // When loading secrets providers - result := runtime.LoadSecretsProviders() - - // Then should return the same runtime instance - if result != runtime { - t.Error("Expected LoadSecretsProviders to return the same runtime instance") - } - - // And no error should be set - if runtime.err != nil { - t.Errorf("Expected no error, got %v", runtime.err) - } - - // And no secrets providers should be loaded - if runtime.SecretsProviders.Sops != nil { - t.Error("Expected Sops provider to not be loaded when no secrets file exists") - } - if runtime.SecretsProviders.Onepassword != nil { - t.Error("Expected OnePassword provider to not be loaded when no vaults configured") - } - }) -} - func TestRuntime_InstallHook(t *testing.T) { t.Run("InstallsHookSuccessfully", func(t *testing.T) { // Given a runtime with loaded shell