From 16eccfcd8d5c64ef0150d1f64f52d7b3fa2d48e0 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:01:03 -0400 Subject: [PATCH 1/3] refactor(runtime): Pass Dependencies object to Runtime constructor We need the ability to easily pass mocked dependencies in to the runtime. Separates `Dependencies` into its own struct for this purpose. Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- cmd/hook.go | 12 ++-- pkg/runtime/runtime.go | 129 +++++++++++++++++++----------------- pkg/runtime/runtime_test.go | 71 ++++++++++++-------- 3 files changed, 119 insertions(+), 93 deletions(-) diff --git a/cmd/hook.go b/cmd/hook.go index c582ec1d2..8e2507100 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -15,19 +15,17 @@ var hookCmd = &cobra.Command{ SilenceUsage: true, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - // Get shared dependency injector from context - injector := cmd.Context().Value(injectorKey).(di.Injector) + deps := &runtime.Dependencies{ + Injector: cmd.Context().Value(injectorKey).(di.Injector), + } // Create Runtime and execute hook installation - err := runtime.NewRuntime(injector). + if err := runtime.NewRuntime(deps). LoadShell(). InstallHook(args[0]). - Do() - - if err != nil { + Do(); err != nil { return fmt.Errorf("Error installing hook: %w", err) } - return nil }, } diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index ea3f13f42..fe3ca7614 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -25,69 +25,72 @@ import ( // Types // ============================================================================= -// Runtime encapsulates all core Windsor CLI runtime dependencies for injection. -type Runtime struct { - - // Core dependencies - shell shell.Shell - configHandler config.ConfigHandler - toolsManager tools.ToolsManager - envPrinters struct { - awsEnv env.EnvPrinter - azureEnv env.EnvPrinter - dockerEnv env.EnvPrinter - kubeEnv env.EnvPrinter - talosEnv env.EnvPrinter - terraformEnv env.EnvPrinter - windsorEnv env.EnvPrinter +// Dependencies contains all the dependencies that Runtime might need. +// This allows for explicit dependency injection without complex DI frameworks. +type Dependencies struct { + Injector di.Injector + Shell shell.Shell + ConfigHandler config.ConfigHandler + ToolsManager tools.ToolsManager + EnvPrinters struct { + AwsEnv env.EnvPrinter + AzureEnv env.EnvPrinter + DockerEnv env.EnvPrinter + KubeEnv env.EnvPrinter + TalosEnv env.EnvPrinter + TerraformEnv env.EnvPrinter + WindsorEnv env.EnvPrinter } - secretsProviders struct { - sops secrets.SecretsProvider - onepassword secrets.SecretsProvider + SecretsProviders struct { + Sops secrets.SecretsProvider + Onepassword secrets.SecretsProvider } - - // Blueprint dependencies - blueprintHandler blueprint.BlueprintHandler - artifactBuilder artifact.Artifact - generators struct { - gitGenerator generators.Generator - terraformGenerator generators.Generator + BlueprintHandler blueprint.BlueprintHandler + ArtifactBuilder artifact.Artifact + Generators struct { + GitGenerator generators.Generator + TerraformGenerator generators.Generator } - terraformResolver terraform.ModuleResolver - - // Cluster dependencies - clusterClient cluster.ClusterClient - k8sManager kubernetes.KubernetesManager - - // Workstation dependencies - workstation struct { - virt virt.Virt - services struct { - dnsService services.Service - gitLivereloadService services.Service - localstackService services.Service - registryServices map[string]services.Service - talosServices map[string]services.Service + TerraformResolver terraform.ModuleResolver + ClusterClient cluster.ClusterClient + K8sManager kubernetes.KubernetesManager + Workstation struct { + Virt virt.Virt + Services struct { + DnsService services.Service + GitLivereloadService services.Service + LocalstackService services.Service + RegistryServices map[string]services.Service + TalosServices map[string]services.Service } - network network.NetworkManager - ssh ssh.SSHClient + Network network.NetworkManager + Ssh ssh.SSHClient } +} - // Error +// Runtime encapsulates all core Windsor CLI runtime dependencies. +type Runtime struct { + Dependencies err error - - // Injector (to be deprecated) - injector di.Injector } // ============================================================================= // Constructor // ============================================================================= -// NewRuntime creates a new Runtime instance -func NewRuntime(injector di.Injector) *Runtime { +// NewRuntime creates a new Runtime instance with the provided dependencies. +func NewRuntime(deps ...*Dependencies) *Runtime { + var depsVal *Dependencies + if len(deps) > 0 && deps[0] != nil { + depsVal = deps[0] + } else { + depsVal = &Dependencies{} + } + if depsVal.Injector == nil { + depsVal.Injector = di.NewInjector() + } return &Runtime{ - injector: injector, + Dependencies: *depsVal, } } @@ -95,33 +98,39 @@ func NewRuntime(injector di.Injector) *Runtime { // Public Methods // ============================================================================= -// Do serves as the final execution point in the Windsor application lifecycle. -// It returns the cumulative error state from all preceding runtime operations, ensuring that the -// top-level caller receives any error reported by the Windsor runtime subsystems. -// -// Do does not perform any additional processing; it solely propagates the stored error value, -// which must be set by lower-level runtime methods. If no error has occurred, Do returns nil. +// Do returns the cumulative error state from all preceding runtime operations. func (r *Runtime) Do() error { return r.err } -// LoadShell loads the shell dependency from the injector. +// LoadShell loads the shell dependency, creating a new default shell if none exists. func (r *Runtime) LoadShell() *Runtime { if r.err != nil { return r } - r.shell = shell.NewDefaultShell(r.injector) - r.injector.Register("shell", r.shell) + + if r.Shell == nil { + r.Shell = shell.NewDefaultShell(r.Injector) + r.Injector.Register("shell", r.Shell) + } + return r +} return r } // InstallHook installs a shell hook for the specified shell type. -// It requires the shell to be loaded first via LoadShell(). func (r *Runtime) InstallHook(shellType string) *Runtime { if r.err != nil { return r } - if r.shell == nil { + if r.Shell == nil { + r.err = fmt.Errorf("shell not loaded - call LoadShell() first") + return r + } + r.err = r.Shell.InstallHook(shellType) + return r +} + r.err = fmt.Errorf("shell not loaded - call LoadShell() first") return r } diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 95c278c9d..ee68d1470 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -18,22 +18,13 @@ import ( // Test Setup // ============================================================================= -type Mocks struct { - Injector di.Injector - MockShell *shell.MockShell -} - // setupMocks creates a new set of mocks for testing -func setupMocks(t *testing.T) *Mocks { +func setupMocks(t *testing.T) *Dependencies { t.Helper() - injector := di.NewInjector() - mockShell := shell.NewMockShell() - injector.Register("shell", mockShell) - - return &Mocks{ - Injector: injector, - MockShell: mockShell, + return &Dependencies{ + Injector: di.NewInjector(), + Shell: shell.NewMockShell(), } } @@ -42,19 +33,19 @@ func setupMocks(t *testing.T) *Mocks { // ============================================================================= func TestRuntime_NewRuntime(t *testing.T) { - t.Run("CreatesRuntimeWithInjector", func(t *testing.T) { - // Given an injector + t.Run("CreatesRuntimeWithDependencies", func(t *testing.T) { + // Given dependencies mocks := setupMocks(t) // When creating a new runtime - runtime := NewRuntime(mocks.Injector) + runtime := NewRuntime(mocks) // Then runtime should be created successfully if runtime == nil { t.Error("Expected runtime to be created") } - if runtime.injector != mocks.Injector { + if runtime.Injector != mocks.Injector { t.Error("Expected injector to be set") } }) @@ -62,9 +53,9 @@ func TestRuntime_NewRuntime(t *testing.T) { func TestRuntime_LoadShell(t *testing.T) { t.Run("LoadsShellSuccessfully", func(t *testing.T) { - // Given a runtime with injector + // Given a runtime with dependencies mocks := setupMocks(t) - runtime := NewRuntime(mocks.Injector) + runtime := NewRuntime(mocks) // When loading shell result := runtime.LoadShell() @@ -75,7 +66,7 @@ func TestRuntime_LoadShell(t *testing.T) { } // And shell should be loaded - if runtime.shell == nil { + if runtime.Shell == nil { t.Error("Expected shell to be loaded") } @@ -85,10 +76,38 @@ func TestRuntime_LoadShell(t *testing.T) { } }) + 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 - mocks := setupMocks(t) - runtime := NewRuntime(mocks.Injector) + // Given a runtime with an existing error (no pre-loaded dependencies) + runtime := NewRuntime() runtime.err = errors.New("existing error") // When loading shell @@ -100,7 +119,7 @@ func TestRuntime_LoadShell(t *testing.T) { } // And shell should not be loaded - if runtime.shell != nil { + if runtime.Shell != nil { t.Error("Expected shell to not be loaded when error exists") } @@ -180,7 +199,7 @@ func TestRuntime_Do(t *testing.T) { t.Run("ReturnsNilWhenNoError", func(t *testing.T) { // Given a runtime with no error mocks := setupMocks(t) - runtime := NewRuntime(mocks.Injector) + runtime := NewRuntime(mocks) // When calling Do err := runtime.Do() @@ -194,7 +213,7 @@ func TestRuntime_Do(t *testing.T) { t.Run("ReturnsErrorWhenErrorSet", func(t *testing.T) { // Given a runtime with an error mocks := setupMocks(t) - runtime := NewRuntime(mocks.Injector) + runtime := NewRuntime(mocks) expectedError := errors.New("test error") runtime.err = expectedError From 7bd4dcf2e173b59139baa0748b533d37d60ef211 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:04:52 -0400 Subject: [PATCH 2/3] Fix syntax errros Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- pkg/runtime/runtime.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index fe3ca7614..cebebf371 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -115,8 +115,6 @@ func (r *Runtime) LoadShell() *Runtime { } return r } - return r -} // InstallHook installs a shell hook for the specified shell type. func (r *Runtime) InstallHook(shellType string) *Runtime { @@ -130,10 +128,3 @@ func (r *Runtime) InstallHook(shellType string) *Runtime { r.err = r.Shell.InstallHook(shellType) return r } - - r.err = fmt.Errorf("shell not loaded - call LoadShell() first") - return r - } - r.err = r.shell.InstallHook(shellType) - return r -} From 75bcddbc47a47df6bfa11e2817703347bfe84c96 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:08:18 -0400 Subject: [PATCH 3/3] Fix argument type Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- pkg/runtime/runtime_test.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index ee68d1470..415ec769a 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -134,7 +134,7 @@ func TestRuntime_InstallHook(t *testing.T) { t.Run("InstallsHookSuccessfully", func(t *testing.T) { // Given a runtime with loaded shell mocks := setupMocks(t) - runtime := NewRuntime(mocks.Injector).LoadShell() + runtime := NewRuntime(mocks).LoadShell() // When installing hook result := runtime.InstallHook("bash") @@ -151,9 +151,8 @@ func TestRuntime_InstallHook(t *testing.T) { }) t.Run("ReturnsErrorWhenShellNotLoaded", func(t *testing.T) { - // Given a runtime without loaded shell - mocks := setupMocks(t) - runtime := NewRuntime(mocks.Injector) + // Given a runtime without loaded shell (no pre-loaded dependencies) + runtime := NewRuntime() // When installing hook result := runtime.InstallHook("bash") @@ -175,9 +174,8 @@ func TestRuntime_InstallHook(t *testing.T) { }) t.Run("ReturnsEarlyOnExistingError", func(t *testing.T) { - // Given a runtime with an existing error - mocks := setupMocks(t) - runtime := NewRuntime(mocks.Injector) + // Given a runtime with an existing error (no pre-loaded dependencies) + runtime := NewRuntime() runtime.err = errors.New("existing error") // When installing hook