Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions cmd/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
}
Expand Down
122 changes: 61 additions & 61 deletions pkg/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,106 +25,106 @@ 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,
}
}

// =============================================================================
// 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
}

// 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)
r.err = r.Shell.InstallHook(shellType)
return r
}
83 changes: 50 additions & 33 deletions pkg/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}

Expand All @@ -42,29 +33,29 @@ 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")
}
})
}

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()
Expand All @@ -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")
}

Expand All @@ -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
Expand All @@ -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")
}

Expand All @@ -115,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")
Expand All @@ -132,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")
Expand All @@ -156,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
Expand All @@ -180,7 +197,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()
Expand All @@ -194,7 +211,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

Expand Down
Loading