From 7c3eada361e52202946aaecee6adc36020fe3832 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Mon, 27 Oct 2025 09:38:30 -0400 Subject: [PATCH 1/2] Reorganize Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- cmd/root_test.go | 10 ++--- pkg/{env => environment/envvars}/aws_env.go | 2 +- .../envvars}/aws_env_test.go | 2 +- pkg/{env => environment/envvars}/azure_env.go | 2 +- .../envvars}/azure_env_test.go | 2 +- .../envvars}/docker_env.go | 2 +- .../envvars}/docker_env_test.go | 2 +- pkg/{env => environment/envvars}/env.go | 2 +- pkg/{env => environment/envvars}/env_test.go | 2 +- pkg/{env => environment/envvars}/kube_env.go | 2 +- .../envvars}/kube_env_test.go | 2 +- pkg/{env => environment/envvars}/mock_env.go | 2 +- .../envvars}/mock_env_test.go | 2 +- pkg/{env => environment/envvars}/shims.go | 2 +- .../envvars}/shims_test.go | 2 +- pkg/{env => environment/envvars}/talos_env.go | 2 +- .../envvars}/talos_env_test.go | 2 +- .../envvars}/terraform_env.go | 2 +- .../envvars}/terraform_env_test.go | 2 +- .../envvars}/windsor_env.go | 2 +- .../envvars}/windsor_env_test.go | 2 +- .../tools/mock_tools_manager.go | 0 .../tools/mock_tools_manager_test.go | 0 pkg/{ => environment}/tools/shims.go | 0 pkg/{ => environment}/tools/tools_manager.go | 0 .../tools/tools_manager_test.go | 0 pkg/pipelines/check.go | 2 +- pkg/pipelines/check_test.go | 2 +- pkg/pipelines/down.go | 4 +- pkg/pipelines/down_test.go | 10 ++--- pkg/pipelines/init.go | 8 ++-- pkg/pipelines/init_test.go | 2 +- pkg/pipelines/pipeline.go | 4 +- pkg/pipelines/pipeline_test.go | 7 ++-- pkg/pipelines/up.go | 6 +-- pkg/pipelines/up_test.go | 6 +-- pkg/runtime/runtime.go | 24 +++++------ pkg/runtime/runtime_loaders.go | 16 +++---- pkg/runtime/runtime_test.go | 42 +++++++++---------- pkg/stack/windsor_stack.go | 6 +-- pkg/stack/windsor_stack_test.go | 4 +- 41 files changed, 95 insertions(+), 98 deletions(-) rename pkg/{env => environment/envvars}/aws_env.go (99%) rename pkg/{env => environment/envvars}/aws_env_test.go (99%) rename pkg/{env => environment/envvars}/azure_env.go (99%) rename pkg/{env => environment/envvars}/azure_env_test.go (99%) rename pkg/{env => environment/envvars}/docker_env.go (99%) rename pkg/{env => environment/envvars}/docker_env_test.go (99%) rename pkg/{env => environment/envvars}/env.go (99%) rename pkg/{env => environment/envvars}/env_test.go (99%) rename pkg/{env => environment/envvars}/kube_env.go (99%) rename pkg/{env => environment/envvars}/kube_env_test.go (99%) rename pkg/{env => environment/envvars}/mock_env.go (99%) rename pkg/{env => environment/envvars}/mock_env_test.go (99%) rename pkg/{env => environment/envvars}/shims.go (99%) rename pkg/{env => environment/envvars}/shims_test.go (98%) rename pkg/{env => environment/envvars}/talos_env.go (99%) rename pkg/{env => environment/envvars}/talos_env_test.go (99%) rename pkg/{env => environment/envvars}/terraform_env.go (99%) rename pkg/{env => environment/envvars}/terraform_env_test.go (99%) rename pkg/{env => environment/envvars}/windsor_env.go (99%) rename pkg/{env => environment/envvars}/windsor_env_test.go (99%) rename pkg/{ => environment}/tools/mock_tools_manager.go (100%) rename pkg/{ => environment}/tools/mock_tools_manager_test.go (100%) rename pkg/{ => environment}/tools/shims.go (100%) rename pkg/{ => environment}/tools/tools_manager.go (100%) rename pkg/{ => environment}/tools/tools_manager_test.go (100%) diff --git a/cmd/root_test.go b/cmd/root_test.go index 021b3b66c..f6928f656 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -16,7 +16,7 @@ import ( blueprintpkg "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" "github.com/windsorcli/cli/pkg/kubernetes" "github.com/windsorcli/cli/pkg/secrets" "github.com/windsorcli/cli/pkg/shell" @@ -31,7 +31,7 @@ type Mocks struct { ConfigHandler config.ConfigHandler Shell *shell.MockShell SecretsProvider *secrets.MockSecretsProvider - EnvPrinter *env.MockEnvPrinter + EnvPrinter *envvars.MockEnvPrinter Shims *Shims BlueprintHandler *blueprintpkg.MockBlueprintHandler } @@ -118,7 +118,7 @@ func setupMocks(t *testing.T, opts ...*SetupOptions) *Mocks { injector.Register("secretsProvider", mockSecretsProvider) // Create and register mock env printer - mockEnvPrinter := env.NewMockEnvPrinter() + mockEnvPrinter := envvars.NewMockEnvPrinter() // PrintFunc removed - functionality now in runtime mockEnvPrinter.PostEnvHookFunc = func(directory ...string) error { return nil @@ -126,14 +126,14 @@ func setupMocks(t *testing.T, opts ...*SetupOptions) *Mocks { injector.Register("envPrinter", mockEnvPrinter) // Create and register additional mock env printers - mockWindsorEnvPrinter := env.NewMockEnvPrinter() + mockWindsorEnvPrinter := envvars.NewMockEnvPrinter() // PrintFunc removed - functionality now in runtime mockWindsorEnvPrinter.PostEnvHookFunc = func(directory ...string) error { return nil } injector.Register("windsorEnvPrinter", mockWindsorEnvPrinter) - mockDockerEnvPrinter := env.NewMockEnvPrinter() + mockDockerEnvPrinter := envvars.NewMockEnvPrinter() // PrintFunc removed - functionality now in runtime mockDockerEnvPrinter.PostEnvHookFunc = func(directory ...string) error { return nil diff --git a/pkg/env/aws_env.go b/pkg/environment/envvars/aws_env.go similarity index 99% rename from pkg/env/aws_env.go rename to pkg/environment/envvars/aws_env.go index a30fab7eb..2aa8d4fcf 100644 --- a/pkg/env/aws_env.go +++ b/pkg/environment/envvars/aws_env.go @@ -3,7 +3,7 @@ // The AwsEnvPrinter handles AWS profile, endpoint, and S3 configuration settings, // ensuring proper AWS CLI integration and environment setup for AWS operations. -package env +package envvars import ( "fmt" diff --git a/pkg/env/aws_env_test.go b/pkg/environment/envvars/aws_env_test.go similarity index 99% rename from pkg/env/aws_env_test.go rename to pkg/environment/envvars/aws_env_test.go index 8bb2fcf48..72ee0a94b 100644 --- a/pkg/env/aws_env_test.go +++ b/pkg/environment/envvars/aws_env_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "fmt" diff --git a/pkg/env/azure_env.go b/pkg/environment/envvars/azure_env.go similarity index 99% rename from pkg/env/azure_env.go rename to pkg/environment/envvars/azure_env.go index 8443f1dc5..df9395588 100644 --- a/pkg/env/azure_env.go +++ b/pkg/environment/envvars/azure_env.go @@ -3,7 +3,7 @@ // The AzureEnvPrinter handles Azure configuration settings and environment setup, // ensuring proper Azure CLI integration and environment setup for operations. -package env +package envvars import ( "fmt" diff --git a/pkg/env/azure_env_test.go b/pkg/environment/envvars/azure_env_test.go similarity index 99% rename from pkg/env/azure_env_test.go rename to pkg/environment/envvars/azure_env_test.go index 98a86ee10..762ed96ac 100644 --- a/pkg/env/azure_env_test.go +++ b/pkg/environment/envvars/azure_env_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "fmt" diff --git a/pkg/env/docker_env.go b/pkg/environment/envvars/docker_env.go similarity index 99% rename from pkg/env/docker_env.go rename to pkg/environment/envvars/docker_env.go index be0c0efdf..f246b2b4b 100644 --- a/pkg/env/docker_env.go +++ b/pkg/environment/envvars/docker_env.go @@ -3,7 +3,7 @@ // The DockerEnvPrinter handles Docker host, context, and registry configuration settings, // ensuring proper Docker CLI integration and environment setup for container operations. -package env +package envvars import ( "fmt" diff --git a/pkg/env/docker_env_test.go b/pkg/environment/envvars/docker_env_test.go similarity index 99% rename from pkg/env/docker_env_test.go rename to pkg/environment/envvars/docker_env_test.go index e87f2d4c2..adbe6996f 100644 --- a/pkg/env/docker_env_test.go +++ b/pkg/environment/envvars/docker_env_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "errors" diff --git a/pkg/env/env.go b/pkg/environment/envvars/env.go similarity index 99% rename from pkg/env/env.go rename to pkg/environment/envvars/env.go index bd0a696bf..86833cba1 100644 --- a/pkg/env/env.go +++ b/pkg/environment/envvars/env.go @@ -3,7 +3,7 @@ // The EnvPrinter acts as the central environment orchestrator for the application, // coordinating environment variable management, shell integration, and configuration persistence. -package env +package envvars import ( "fmt" diff --git a/pkg/env/env_test.go b/pkg/environment/envvars/env_test.go similarity index 99% rename from pkg/env/env_test.go rename to pkg/environment/envvars/env_test.go index c5ed706c0..8be0a5d1f 100644 --- a/pkg/env/env_test.go +++ b/pkg/environment/envvars/env_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "os" diff --git a/pkg/env/kube_env.go b/pkg/environment/envvars/kube_env.go similarity index 99% rename from pkg/env/kube_env.go rename to pkg/environment/envvars/kube_env.go index 07ab6bf75..af92840bc 100644 --- a/pkg/env/kube_env.go +++ b/pkg/environment/envvars/kube_env.go @@ -3,7 +3,7 @@ // The KubeEnvPrinter handles kubeconfig, context, and persistent volume configuration settings, // ensuring proper kubectl integration and environment setup for Kubernetes operations. -package env +package envvars import ( "context" diff --git a/pkg/env/kube_env_test.go b/pkg/environment/envvars/kube_env_test.go similarity index 99% rename from pkg/env/kube_env_test.go rename to pkg/environment/envvars/kube_env_test.go index a832ddc07..8c2f34fe5 100644 --- a/pkg/env/kube_env_test.go +++ b/pkg/environment/envvars/kube_env_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "errors" diff --git a/pkg/env/mock_env.go b/pkg/environment/envvars/mock_env.go similarity index 99% rename from pkg/env/mock_env.go rename to pkg/environment/envvars/mock_env.go index 2b636fcca..ac86aca6a 100644 --- a/pkg/env/mock_env.go +++ b/pkg/environment/envvars/mock_env.go @@ -3,7 +3,7 @@ // The MockEnvPrinter enables testing of environment-dependent functionality, // allowing for controlled simulation of environment operations in tests. -package env +package envvars // ============================================================================= // Types diff --git a/pkg/env/mock_env_test.go b/pkg/environment/envvars/mock_env_test.go similarity index 99% rename from pkg/env/mock_env_test.go rename to pkg/environment/envvars/mock_env_test.go index c488b6226..6f3f4fa80 100644 --- a/pkg/env/mock_env_test.go +++ b/pkg/environment/envvars/mock_env_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "fmt" diff --git a/pkg/env/shims.go b/pkg/environment/envvars/shims.go similarity index 99% rename from pkg/env/shims.go rename to pkg/environment/envvars/shims.go index 5bfc641d1..da68e102c 100644 --- a/pkg/env/shims.go +++ b/pkg/environment/envvars/shims.go @@ -3,7 +3,7 @@ // It serves as a testing aid by allowing system calls to be intercepted // It enables dependency injection and test isolation for system-level operations -package env +package envvars import ( "crypto/rand" diff --git a/pkg/env/shims_test.go b/pkg/environment/envvars/shims_test.go similarity index 98% rename from pkg/env/shims_test.go rename to pkg/environment/envvars/shims_test.go index 76768cf73..28663b4da 100644 --- a/pkg/env/shims_test.go +++ b/pkg/environment/envvars/shims_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "testing" diff --git a/pkg/env/talos_env.go b/pkg/environment/envvars/talos_env.go similarity index 99% rename from pkg/env/talos_env.go rename to pkg/environment/envvars/talos_env.go index ce95d18cc..39f71e0b2 100644 --- a/pkg/env/talos_env.go +++ b/pkg/environment/envvars/talos_env.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "fmt" diff --git a/pkg/env/talos_env_test.go b/pkg/environment/envvars/talos_env_test.go similarity index 99% rename from pkg/env/talos_env_test.go rename to pkg/environment/envvars/talos_env_test.go index 428449d92..328fde89f 100644 --- a/pkg/env/talos_env_test.go +++ b/pkg/environment/envvars/talos_env_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "errors" diff --git a/pkg/env/terraform_env.go b/pkg/environment/envvars/terraform_env.go similarity index 99% rename from pkg/env/terraform_env.go rename to pkg/environment/envvars/terraform_env.go index 7ec30c9ff..0c6df3aec 100644 --- a/pkg/env/terraform_env.go +++ b/pkg/environment/envvars/terraform_env.go @@ -3,7 +3,7 @@ // The TerraformEnvPrinter handles backend configuration, variable files, and state management, // ensuring proper Terraform CLI integration and environment setup for infrastructure operations. -package env +package envvars import ( "fmt" diff --git a/pkg/env/terraform_env_test.go b/pkg/environment/envvars/terraform_env_test.go similarity index 99% rename from pkg/env/terraform_env_test.go rename to pkg/environment/envvars/terraform_env_test.go index 81dcdffcb..6c0ce731c 100644 --- a/pkg/env/terraform_env_test.go +++ b/pkg/environment/envvars/terraform_env_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "fmt" diff --git a/pkg/env/windsor_env.go b/pkg/environment/envvars/windsor_env.go similarity index 99% rename from pkg/env/windsor_env.go rename to pkg/environment/envvars/windsor_env.go index 50c3b4a1a..54ed7f68c 100644 --- a/pkg/env/windsor_env.go +++ b/pkg/environment/envvars/windsor_env.go @@ -3,7 +3,7 @@ // The WindsorEnvPrinter handles context, project root, and secrets management, // ensuring proper Windsor CLI integration and environment setup for application operations. -package env +package envvars import ( "fmt" diff --git a/pkg/env/windsor_env_test.go b/pkg/environment/envvars/windsor_env_test.go similarity index 99% rename from pkg/env/windsor_env_test.go rename to pkg/environment/envvars/windsor_env_test.go index 51a491ceb..298e126bc 100644 --- a/pkg/env/windsor_env_test.go +++ b/pkg/environment/envvars/windsor_env_test.go @@ -1,4 +1,4 @@ -package env +package envvars import ( "fmt" diff --git a/pkg/tools/mock_tools_manager.go b/pkg/environment/tools/mock_tools_manager.go similarity index 100% rename from pkg/tools/mock_tools_manager.go rename to pkg/environment/tools/mock_tools_manager.go diff --git a/pkg/tools/mock_tools_manager_test.go b/pkg/environment/tools/mock_tools_manager_test.go similarity index 100% rename from pkg/tools/mock_tools_manager_test.go rename to pkg/environment/tools/mock_tools_manager_test.go diff --git a/pkg/tools/shims.go b/pkg/environment/tools/shims.go similarity index 100% rename from pkg/tools/shims.go rename to pkg/environment/tools/shims.go diff --git a/pkg/tools/tools_manager.go b/pkg/environment/tools/tools_manager.go similarity index 100% rename from pkg/tools/tools_manager.go rename to pkg/environment/tools/tools_manager.go diff --git a/pkg/tools/tools_manager_test.go b/pkg/environment/tools/tools_manager_test.go similarity index 100% rename from pkg/tools/tools_manager_test.go rename to pkg/environment/tools/tools_manager_test.go diff --git a/pkg/pipelines/check.go b/pkg/pipelines/check.go index c7bb0c574..650cd3c0e 100644 --- a/pkg/pipelines/check.go +++ b/pkg/pipelines/check.go @@ -7,8 +7,8 @@ import ( "github.com/windsorcli/cli/pkg/cluster" "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/environment/tools" "github.com/windsorcli/cli/pkg/kubernetes" - "github.com/windsorcli/cli/pkg/tools" ) // The CheckPipeline is a specialized component that manages tool version checking and node health checking functionality. diff --git a/pkg/pipelines/check_test.go b/pkg/pipelines/check_test.go index b0c85c647..ab202eef4 100644 --- a/pkg/pipelines/check_test.go +++ b/pkg/pipelines/check_test.go @@ -11,9 +11,9 @@ import ( "github.com/windsorcli/cli/pkg/cluster" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/environment/tools" "github.com/windsorcli/cli/pkg/kubernetes" "github.com/windsorcli/cli/pkg/shell" - "github.com/windsorcli/cli/pkg/tools" ) // mockFileInfo implements os.FileInfo for testing diff --git a/pkg/pipelines/down.go b/pkg/pipelines/down.go index 0fd963513..edf128e54 100644 --- a/pkg/pipelines/down.go +++ b/pkg/pipelines/down.go @@ -8,7 +8,7 @@ import ( "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" "github.com/windsorcli/cli/pkg/kubernetes" "github.com/windsorcli/cli/pkg/shell" "github.com/windsorcli/cli/pkg/stack" @@ -35,7 +35,7 @@ type DownPipeline struct { blueprintHandler blueprint.BlueprintHandler kubernetesClient kubernetes.KubernetesClient kubernetesManager kubernetes.KubernetesManager - envPrinters []env.EnvPrinter + envPrinters []envvars.EnvPrinter } // ============================================================================= diff --git a/pkg/pipelines/down_test.go b/pkg/pipelines/down_test.go index d7b06f164..109b990a0 100644 --- a/pkg/pipelines/down_test.go +++ b/pkg/pipelines/down_test.go @@ -9,7 +9,7 @@ import ( "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/config" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" "github.com/windsorcli/cli/pkg/kubernetes" "github.com/windsorcli/cli/pkg/shell" "github.com/windsorcli/cli/pkg/stack" @@ -100,8 +100,8 @@ contexts: baseMocks.Injector.Register("blueprintHandler", mockBlueprintHandler) // Setup env printers - mockEnvPrinters := []env.EnvPrinter{} - windsorEnv := env.NewMockEnvPrinter() + mockEnvPrinters := []envvars.EnvPrinter{} + windsorEnv := envvars.NewMockEnvPrinter() windsorEnv.InitializeFunc = func() error { return nil } windsorEnv.GetEnvVarsFunc = func() (map[string]string, error) { return map[string]string{"WINDSOR_TEST": "true"}, nil @@ -305,13 +305,13 @@ func TestDownPipeline_Initialize(t *testing.T) { } // Create a failing env printer and register it - failingEnvPrinter := env.NewMockEnvPrinter() + failingEnvPrinter := envvars.NewMockEnvPrinter() failingEnvPrinter.InitializeFunc = func() error { return fmt.Errorf("env printer initialization failed") } // Set the env printers directly to include the failing one - pipeline.envPrinters = []env.EnvPrinter{failingEnvPrinter} + pipeline.envPrinters = []envvars.EnvPrinter{failingEnvPrinter} // When initializing the env printers var initErr error diff --git a/pkg/pipelines/init.go b/pkg/pipelines/init.go index 248c2b714..213d7ea6b 100644 --- a/pkg/pipelines/init.go +++ b/pkg/pipelines/init.go @@ -13,12 +13,12 @@ import ( "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/constants" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" + "github.com/windsorcli/cli/pkg/environment/tools" "github.com/windsorcli/cli/pkg/generators" "github.com/windsorcli/cli/pkg/shell" "github.com/windsorcli/cli/pkg/stack" "github.com/windsorcli/cli/pkg/terraform" - "github.com/windsorcli/cli/pkg/tools" "github.com/windsorcli/cli/pkg/workstation/network" "github.com/windsorcli/cli/pkg/workstation/services" "github.com/windsorcli/cli/pkg/workstation/virt" @@ -118,7 +118,7 @@ func (p *InitPipeline) Initialize(injector di.Injector, ctx context.Context) err p.toolsManager = p.withToolsManager() if p.injector.Resolve("terraformEnv") == nil { - terraformEnv := env.NewTerraformEnvPrinter(p.injector) + terraformEnv := envvars.NewTerraformEnvPrinter(p.injector) p.injector.Register("terraformEnv", terraformEnv) } @@ -131,7 +131,6 @@ func (p *InitPipeline) Initialize(injector di.Injector, ctx context.Context) err } p.generators = generators - services, err := p.withServices() if err != nil { return fmt.Errorf("failed to create services: %w", err) @@ -186,7 +185,6 @@ func (p *InitPipeline) Initialize(injector di.Injector, ctx context.Context) err } } - for _, service := range p.services { if err := service.Initialize(); err != nil { return fmt.Errorf("failed to initialize service: %w", err) diff --git a/pkg/pipelines/init_test.go b/pkg/pipelines/init_test.go index 70fc3017f..8bcf785ab 100644 --- a/pkg/pipelines/init_test.go +++ b/pkg/pipelines/init_test.go @@ -13,10 +13,10 @@ import ( "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/environment/tools" "github.com/windsorcli/cli/pkg/kubernetes" "github.com/windsorcli/cli/pkg/shell" "github.com/windsorcli/cli/pkg/stack" - "github.com/windsorcli/cli/pkg/tools" "github.com/windsorcli/cli/pkg/workstation/virt" ) diff --git a/pkg/pipelines/pipeline.go b/pkg/pipelines/pipeline.go index b925db04e..dad9fdd63 100644 --- a/pkg/pipelines/pipeline.go +++ b/pkg/pipelines/pipeline.go @@ -13,7 +13,8 @@ import ( "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/constants" "github.com/windsorcli/cli/pkg/di" - envpkg "github.com/windsorcli/cli/pkg/env" + envpkg "github.com/windsorcli/cli/pkg/environment/envvars" + "github.com/windsorcli/cli/pkg/environment/tools" "github.com/windsorcli/cli/pkg/generators" "github.com/windsorcli/cli/pkg/kubernetes" "github.com/windsorcli/cli/pkg/secrets" @@ -21,7 +22,6 @@ import ( "github.com/windsorcli/cli/pkg/shell/ssh" "github.com/windsorcli/cli/pkg/stack" "github.com/windsorcli/cli/pkg/terraform" - "github.com/windsorcli/cli/pkg/tools" "github.com/windsorcli/cli/pkg/workstation/network" "github.com/windsorcli/cli/pkg/workstation/services" "github.com/windsorcli/cli/pkg/workstation/virt" diff --git a/pkg/pipelines/pipeline_test.go b/pkg/pipelines/pipeline_test.go index ba41e13c3..7eb7cfaac 100644 --- a/pkg/pipelines/pipeline_test.go +++ b/pkg/pipelines/pipeline_test.go @@ -17,11 +17,11 @@ import ( "github.com/windsorcli/cli/pkg/cluster" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" + "github.com/windsorcli/cli/pkg/environment/tools" "github.com/windsorcli/cli/pkg/kubernetes" "github.com/windsorcli/cli/pkg/shell" "github.com/windsorcli/cli/pkg/stack" - "github.com/windsorcli/cli/pkg/tools" "github.com/windsorcli/cli/pkg/workstation/virt" ) @@ -192,7 +192,7 @@ network: injector.Register("artifactBuilder", mockArtifactBuilder) // Create and register terraformEnv for stack dependency - terraformEnv := env.NewTerraformEnvPrinter(injector) + terraformEnv := envvars.NewTerraformEnvPrinter(injector) injector.Register("terraformEnv", terraformEnv) return &Mocks{ @@ -2400,7 +2400,6 @@ contexts: }) } - func TestBasePipeline_withToolsManager(t *testing.T) { setup := func(t *testing.T) (*BasePipeline, *Mocks) { pipeline := NewBasePipeline() diff --git a/pkg/pipelines/up.go b/pkg/pipelines/up.go index aea44f891..8149ec986 100644 --- a/pkg/pipelines/up.go +++ b/pkg/pipelines/up.go @@ -6,10 +6,10 @@ import ( "os" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" + "github.com/windsorcli/cli/pkg/environment/tools" "github.com/windsorcli/cli/pkg/shell" "github.com/windsorcli/cli/pkg/stack" - "github.com/windsorcli/cli/pkg/tools" "github.com/windsorcli/cli/pkg/workstation/network" "github.com/windsorcli/cli/pkg/workstation/virt" ) @@ -32,7 +32,7 @@ type UpPipeline struct { containerRuntime virt.ContainerRuntime networkManager network.NetworkManager stack stack.Stack - envPrinters []env.EnvPrinter + envPrinters []envvars.EnvPrinter } // ============================================================================= diff --git a/pkg/pipelines/up_test.go b/pkg/pipelines/up_test.go index 0cda78dfd..16d3452ab 100644 --- a/pkg/pipelines/up_test.go +++ b/pkg/pipelines/up_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/windsorcli/cli/pkg/config" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" + "github.com/windsorcli/cli/pkg/environment/tools" "github.com/windsorcli/cli/pkg/shell" "github.com/windsorcli/cli/pkg/stack" - "github.com/windsorcli/cli/pkg/tools" "github.com/windsorcli/cli/pkg/workstation/network" "github.com/windsorcli/cli/pkg/workstation/virt" ) @@ -99,7 +99,7 @@ contexts: baseMocks.Injector.Register("stack", mockStack) // Setup terraform env mock - mockTerraformEnv := env.NewMockEnvPrinter() + mockTerraformEnv := envvars.NewMockEnvPrinter() mockTerraformEnv.InitializeFunc = func() error { return nil } mockTerraformEnv.GetEnvVarsFunc = func() (map[string]string, error) { return map[string]string{}, nil } baseMocks.Injector.Register("terraformEnv", mockTerraformEnv) diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 167b7e755..c69ef4c2e 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -10,14 +10,14 @@ import ( "github.com/windsorcli/cli/pkg/cluster" "github.com/windsorcli/cli/pkg/config" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" + "github.com/windsorcli/cli/pkg/environment/tools" "github.com/windsorcli/cli/pkg/generators" "github.com/windsorcli/cli/pkg/kubernetes" "github.com/windsorcli/cli/pkg/secrets" "github.com/windsorcli/cli/pkg/shell" "github.com/windsorcli/cli/pkg/shell/ssh" "github.com/windsorcli/cli/pkg/terraform" - "github.com/windsorcli/cli/pkg/tools" "github.com/windsorcli/cli/pkg/types" "github.com/windsorcli/cli/pkg/workstation" "github.com/windsorcli/cli/pkg/workstation/network" @@ -37,13 +37,13 @@ type Dependencies struct { 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 + AwsEnv envvars.EnvPrinter + AzureEnv envvars.EnvPrinter + DockerEnv envvars.EnvPrinter + KubeEnv envvars.EnvPrinter + TalosEnv envvars.EnvPrinter + TerraformEnv envvars.EnvPrinter + WindsorEnv envvars.EnvPrinter } SecretsProviders struct { Sops secrets.SecretsProvider @@ -466,11 +466,11 @@ func (r *Runtime) createWorkstation() (*workstation.Workstation, error) { // getAllEnvPrinters returns all environment printers in field order, ensuring WindsorEnv is last. // This method provides compile-time structure assertions by mirroring the struct layout definition. // Panics at runtime if WindsorEnv is not last to guarantee environment variable precedence. -func (r *Runtime) getAllEnvPrinters() []env.EnvPrinter { +func (r *Runtime) getAllEnvPrinters() []envvars.EnvPrinter { const expectedPrinterCount = 7 _ = [expectedPrinterCount]struct{}{} - allPrinters := []env.EnvPrinter{ + allPrinters := []envvars.EnvPrinter{ r.EnvPrinters.AwsEnv, r.EnvPrinters.AzureEnv, r.EnvPrinters.DockerEnv, @@ -480,7 +480,7 @@ func (r *Runtime) getAllEnvPrinters() []env.EnvPrinter { r.EnvPrinters.WindsorEnv, } - var printers []env.EnvPrinter + var printers []envvars.EnvPrinter for _, printer := range allPrinters { if printer != nil { printers = append(printers, printer) diff --git a/pkg/runtime/runtime_loaders.go b/pkg/runtime/runtime_loaders.go index 7ebe43c92..a939eaf1e 100644 --- a/pkg/runtime/runtime_loaders.go +++ b/pkg/runtime/runtime_loaders.go @@ -10,7 +10,7 @@ import ( "github.com/windsorcli/cli/pkg/blueprint" "github.com/windsorcli/cli/pkg/cluster" "github.com/windsorcli/cli/pkg/config" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" "github.com/windsorcli/cli/pkg/kubernetes" "github.com/windsorcli/cli/pkg/secrets" "github.com/windsorcli/cli/pkg/shell" @@ -214,33 +214,33 @@ func (r *Runtime) LoadEnvVars(opts EnvVarsOptions) *Runtime { } if r.EnvPrinters.AwsEnv == nil && r.ConfigHandler.GetBool("aws.enabled", false) { - r.EnvPrinters.AwsEnv = env.NewAwsEnvPrinter(r.Injector) + r.EnvPrinters.AwsEnv = envvars.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.EnvPrinters.AzureEnv = envvars.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.EnvPrinters.DockerEnv = envvars.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.EnvPrinters.KubeEnv = envvars.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.EnvPrinters.TalosEnv = envvars.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.EnvPrinters.TerraformEnv = envvars.NewTerraformEnvPrinter(r.Injector) r.Injector.Register("terraformEnv", r.EnvPrinters.TerraformEnv) } if r.EnvPrinters.WindsorEnv == nil { - r.EnvPrinters.WindsorEnv = env.NewWindsorEnvPrinter(r.Injector) + r.EnvPrinters.WindsorEnv = envvars.NewWindsorEnvPrinter(r.Injector) r.Injector.Register("windsorEnv", r.EnvPrinters.WindsorEnv) } diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 0e205407e..2c4a5346c 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/windsorcli/cli/pkg/config" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" "github.com/windsorcli/cli/pkg/shell" ) @@ -1252,12 +1252,12 @@ func TestRuntime_PrintAliases(t *testing.T) { runtime := NewRuntime(mocks).LoadShell() // Set up mock environment printers - mockPrinter1 := env.NewMockEnvPrinter() + mockPrinter1 := envvars.NewMockEnvPrinter() mockPrinter1.GetAliasFunc = func() (map[string]string, error) { return map[string]string{"alias1": "command1", "alias2": "command2"}, nil } - mockPrinter2 := env.NewMockEnvPrinter() + mockPrinter2 := envvars.NewMockEnvPrinter() mockPrinter2.GetAliasFunc = func() (map[string]string, error) { return map[string]string{"alias3": "command3"}, nil } @@ -1336,14 +1336,14 @@ func TestRuntime_PrintAliases(t *testing.T) { runtime := NewRuntime(mocks).LoadShell() // Set up mock environment printer that returns error - mockPrinter := env.NewMockEnvPrinter() + mockPrinter := envvars.NewMockEnvPrinter() expectedError := errors.New("alias error") mockPrinter.GetAliasFunc = func() (map[string]string, error) { return nil, expectedError } // Set up WindsorEnv printer to avoid panic - windsorPrinter := env.NewMockEnvPrinter() + windsorPrinter := envvars.NewMockEnvPrinter() windsorPrinter.GetAliasFunc = func() (map[string]string, error) { return map[string]string{}, nil } @@ -1401,13 +1401,13 @@ func TestRuntime_ExecutePostEnvHook(t *testing.T) { hook1Called := false hook2Called := false - mockPrinter1 := env.NewMockEnvPrinter() + mockPrinter1 := envvars.NewMockEnvPrinter() mockPrinter1.PostEnvHookFunc = func(directory ...string) error { hook1Called = true return nil } - mockPrinter2 := env.NewMockEnvPrinter() + mockPrinter2 := envvars.NewMockEnvPrinter() mockPrinter2.PostEnvHookFunc = func(directory ...string) error { hook2Called = true return nil @@ -1444,14 +1444,14 @@ func TestRuntime_ExecutePostEnvHook(t *testing.T) { runtime := NewRuntime(mocks) // Set up mock environment printer that returns error - mockPrinter := env.NewMockEnvPrinter() + mockPrinter := envvars.NewMockEnvPrinter() expectedError := errors.New("hook error") mockPrinter.PostEnvHookFunc = func(directory ...string) error { return expectedError } // Set up WindsorEnv printer to avoid panic - windsorPrinter := env.NewMockEnvPrinter() + windsorPrinter := envvars.NewMockEnvPrinter() windsorPrinter.PostEnvHookFunc = func(directory ...string) error { return nil } @@ -1484,14 +1484,14 @@ func TestRuntime_ExecutePostEnvHook(t *testing.T) { runtime := NewRuntime(mocks) // Set up mock environment printer that returns error - mockPrinter := env.NewMockEnvPrinter() + mockPrinter := envvars.NewMockEnvPrinter() expectedError := errors.New("hook error") mockPrinter.PostEnvHookFunc = func(directory ...string) error { return expectedError } // Set up WindsorEnv printer to avoid panic - windsorPrinter := env.NewMockEnvPrinter() + windsorPrinter := envvars.NewMockEnvPrinter() windsorPrinter.PostEnvHookFunc = func(directory ...string) error { return nil } @@ -1519,13 +1519,13 @@ func TestRuntime_ExecutePostEnvHook(t *testing.T) { runtime := NewRuntime(mocks) // Set up mock environment printers that return errors - mockPrinter1 := env.NewMockEnvPrinter() + mockPrinter1 := envvars.NewMockEnvPrinter() error1 := errors.New("hook error 1") mockPrinter1.PostEnvHookFunc = func(directory ...string) error { return error1 } - mockPrinter2 := env.NewMockEnvPrinter() + mockPrinter2 := envvars.NewMockEnvPrinter() error2 := errors.New("hook error 2") mockPrinter2.PostEnvHookFunc = func(directory ...string) error { return error2 @@ -1560,14 +1560,14 @@ func TestRuntime_ExecutePostEnvHook(t *testing.T) { // Set up one mock environment printer hookCalled := false - mockPrinter := env.NewMockEnvPrinter() + mockPrinter := envvars.NewMockEnvPrinter() mockPrinter.PostEnvHookFunc = func(directory ...string) error { hookCalled = true return nil } // Set up WindsorEnv printer to avoid panic - windsorPrinter := env.NewMockEnvPrinter() + windsorPrinter := envvars.NewMockEnvPrinter() windsorPrinter.PostEnvHookFunc = func(directory ...string) error { return nil } @@ -1603,9 +1603,9 @@ func TestRuntime_getAllEnvPrinters(t *testing.T) { runtime := NewRuntime(mocks) // Set up some mock environment printers - mockPrinter1 := env.NewMockEnvPrinter() - mockPrinter2 := env.NewMockEnvPrinter() - mockPrinter3 := env.NewMockEnvPrinter() + mockPrinter1 := envvars.NewMockEnvPrinter() + mockPrinter2 := envvars.NewMockEnvPrinter() + mockPrinter3 := envvars.NewMockEnvPrinter() runtime.EnvPrinters.AwsEnv = mockPrinter1 runtime.EnvPrinters.AzureEnv = mockPrinter2 @@ -1654,9 +1654,9 @@ func TestRuntime_getAllEnvPrinters(t *testing.T) { runtime := NewRuntime(mocks) // Set up mock environment printers - mockPrinter1 := env.NewMockEnvPrinter() - mockPrinter2 := env.NewMockEnvPrinter() - windsorPrinter := env.NewMockEnvPrinter() + mockPrinter1 := envvars.NewMockEnvPrinter() + mockPrinter2 := envvars.NewMockEnvPrinter() + windsorPrinter := envvars.NewMockEnvPrinter() runtime.EnvPrinters.AwsEnv = mockPrinter1 runtime.EnvPrinters.AzureEnv = mockPrinter2 diff --git a/pkg/stack/windsor_stack.go b/pkg/stack/windsor_stack.go index bfeb2826f..2b2c7c295 100644 --- a/pkg/stack/windsor_stack.go +++ b/pkg/stack/windsor_stack.go @@ -13,7 +13,7 @@ import ( "strings" "github.com/windsorcli/cli/pkg/di" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" ) // ============================================================================= @@ -23,7 +23,7 @@ import ( // WindsorStack is a struct that implements the Stack interface. type WindsorStack struct { BaseStack - terraformEnv *env.TerraformEnvPrinter + terraformEnv *envvars.TerraformEnvPrinter } // ============================================================================= @@ -57,7 +57,7 @@ func (s *WindsorStack) Initialize() error { return fmt.Errorf("terraformEnv not found in dependency injector") } - terraformEnv, ok := terraformEnvInterface.(*env.TerraformEnvPrinter) + terraformEnv, ok := terraformEnvInterface.(*envvars.TerraformEnvPrinter) if !ok { return fmt.Errorf("error resolving terraformEnv") } diff --git a/pkg/stack/windsor_stack_test.go b/pkg/stack/windsor_stack_test.go index f2bdf7a90..56ac2fc74 100644 --- a/pkg/stack/windsor_stack_test.go +++ b/pkg/stack/windsor_stack_test.go @@ -13,7 +13,7 @@ import ( "testing" blueprintv1alpha1 "github.com/windsorcli/cli/api/v1alpha1" - "github.com/windsorcli/cli/pkg/env" + "github.com/windsorcli/cli/pkg/environment/envvars" ) // ============================================================================= @@ -38,7 +38,7 @@ func setupWindsorStackMocks(t *testing.T, opts ...*SetupOptions) *Mocks { } // Register and initialize terraform env printer by default - terraformEnv := env.NewTerraformEnvPrinter(mocks.Injector) + terraformEnv := envvars.NewTerraformEnvPrinter(mocks.Injector) if err := terraformEnv.Initialize(); err != nil { t.Fatalf("Failed to initialize terraform env printer: %v", err) } From e785fc91bb14fab0d326bec061c35eade4764fd9 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Mon, 27 Oct 2025 21:12:49 -0400 Subject: [PATCH 2/2] refactor(environment): Create environment manager The environment manager class wraps environment printers, manages aspects of the local environment. Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- pkg/environment/environment.go | 296 ++++++++++ pkg/environment/environment_test.go | 838 ++++++++++++++++++++++++++++ pkg/types/context.go | 12 + 3 files changed, 1146 insertions(+) create mode 100644 pkg/environment/environment.go create mode 100644 pkg/environment/environment_test.go diff --git a/pkg/environment/environment.go b/pkg/environment/environment.go new file mode 100644 index 000000000..ba47fbe57 --- /dev/null +++ b/pkg/environment/environment.go @@ -0,0 +1,296 @@ +// The Environment package provides a top-level interface for environment management functionality. +// It consolidates environment variable and alias management from the runtime and environment packages, +// providing a unified API for loading, printing, and managing environment state across the Windsor CLI. +// The Environment acts as the primary interface for all environment-related operations, +// coordinating between different environment printers and providing consistent behavior. + +package environment + +import ( + "fmt" + "maps" + + "github.com/windsorcli/cli/pkg/environment/envvars" + "github.com/windsorcli/cli/pkg/environment/tools" + "github.com/windsorcli/cli/pkg/secrets" + "github.com/windsorcli/cli/pkg/types" +) + +// ============================================================================= +// Types +// ============================================================================= + +// EnvironmentExecutionContext holds the execution context for environment operations. +// It embeds the base ExecutionContext and includes all environment-specific dependencies. +type EnvironmentExecutionContext struct { + types.ExecutionContext + + EnvPrinters struct { + AwsEnv envvars.EnvPrinter + AzureEnv envvars.EnvPrinter + DockerEnv envvars.EnvPrinter + KubeEnv envvars.EnvPrinter + TalosEnv envvars.EnvPrinter + TerraformEnv envvars.EnvPrinter + WindsorEnv envvars.EnvPrinter + } + ToolsManager tools.ToolsManager +} + +// Environment manages environment variables and aliases across the Windsor CLI. +// It provides a unified interface for loading, printing, and managing environment state +// by coordinating between different environment printers and handling secrets decryption. +type Environment struct { + *EnvironmentExecutionContext + envVars map[string]string + aliases map[string]string +} + +// ============================================================================= +// Constructor +// ============================================================================= + +// NewEnvironment creates a new Environment instance with the given execution context. +// The constructor sets up all environment printers, the tools manager, and secrets providers based on current configuration. +// It returns an Environment object that will initialize components when LoadEnvironment is called. +func NewEnvironment(ctx *EnvironmentExecutionContext) *Environment { + env := &Environment{ + EnvironmentExecutionContext: ctx, + envVars: make(map[string]string), + aliases: make(map[string]string), + } + + env.initializeEnvPrinters() + env.initializeToolsManager() + env.initializeSecretsProviders() + + return env +} + +// ============================================================================= +// Public Methods +// ============================================================================= + +// LoadEnvironment loads environment variables and aliases from all configured environment printers. +// It initializes all necessary components, optionally loads secrets if requested, and aggregates +// all environment variables and aliases into the Environment instance. Returns an error if any required +// dependency is missing or if any step fails. This method expects the ConfigHandler to be set before invocation. +func (e *Environment) LoadEnvironment(decrypt bool) error { + if e.ConfigHandler == nil { + return fmt.Errorf("config handler not loaded") + } + + if err := e.initializeComponents(); err != nil { + return fmt.Errorf("failed to initialize environment components: %w", err) + } + + if decrypt { + if err := e.loadSecrets(); err != nil { + return fmt.Errorf("failed to load secrets: %w", err) + } + } + + allEnvVars := make(map[string]string) + allAliases := make(map[string]string) + + for _, printer := range e.getAllEnvPrinters() { + if printer != nil { + envVars, err := printer.GetEnvVars() + if err != nil { + return fmt.Errorf("error getting environment variables: %w", err) + } + maps.Copy(allEnvVars, envVars) + + aliases, err := printer.GetAlias() + if err != nil { + return fmt.Errorf("error getting aliases: %w", err) + } + maps.Copy(allAliases, aliases) + } + } + + e.envVars = allEnvVars + e.aliases = allAliases + + return nil +} + +// PrintEnvVars returns all collected environment variables in key=value format. +// If no environment variables are loaded, returns an empty string. +func (e *Environment) PrintEnvVars() string { + if len(e.envVars) > 0 { + return e.Shell.RenderEnvVars(e.envVars, false) + } + return "" +} + +// PrintEnvVarsExport returns all collected environment variables in export key=value format. +// If no environment variables are loaded, returns an empty string. +func (e *Environment) PrintEnvVarsExport() string { + if len(e.envVars) > 0 { + return e.Shell.RenderEnvVars(e.envVars, true) + } + return "" +} + +// PrintAliases returns all collected aliases using the shell's RenderAliases method. +// If no aliases are loaded, returns an empty string. +func (e *Environment) PrintAliases() string { + if len(e.aliases) > 0 { + return e.Shell.RenderAliases(e.aliases) + } + return "" +} + +// ExecutePostEnvHooks executes post-environment hooks for all environment printers. +func (e *Environment) ExecutePostEnvHooks() error { + var firstError error + + for _, printer := range e.getAllEnvPrinters() { + if printer != nil { + if err := printer.PostEnvHook(); err != nil && firstError == nil { + firstError = err + } + } + } + + if firstError != nil { + return fmt.Errorf("failed to execute post env hooks: %w", firstError) + } + + return nil +} + +// GetEnvVars returns a copy of the collected environment variables. +func (e *Environment) GetEnvVars() map[string]string { + result := make(map[string]string) + maps.Copy(result, e.envVars) + return result +} + +// GetAliases returns a copy of the collected aliases. +func (e *Environment) GetAliases() map[string]string { + result := make(map[string]string) + maps.Copy(result, e.aliases) + return result +} + +// ============================================================================= +// Private Methods +// ============================================================================= + +// initializeEnvPrinters initializes environment printers based on configuration settings. +// It creates and registers the appropriate environment printers with the dependency injector +// based on the current configuration state. +func (e *Environment) initializeEnvPrinters() { + if e.EnvPrinters.AwsEnv == nil && e.ConfigHandler.GetBool("aws.enabled", false) { + e.EnvPrinters.AwsEnv = envvars.NewAwsEnvPrinter(e.Injector) + e.Injector.Register("awsEnv", e.EnvPrinters.AwsEnv) + } + if e.EnvPrinters.AzureEnv == nil && e.ConfigHandler.GetBool("azure.enabled", false) { + e.EnvPrinters.AzureEnv = envvars.NewAzureEnvPrinter(e.Injector) + e.Injector.Register("azureEnv", e.EnvPrinters.AzureEnv) + } + if e.EnvPrinters.DockerEnv == nil && e.ConfigHandler.GetBool("docker.enabled", false) { + e.EnvPrinters.DockerEnv = envvars.NewDockerEnvPrinter(e.Injector) + e.Injector.Register("dockerEnv", e.EnvPrinters.DockerEnv) + } + if e.EnvPrinters.KubeEnv == nil && e.ConfigHandler.GetBool("cluster.enabled", false) { + e.EnvPrinters.KubeEnv = envvars.NewKubeEnvPrinter(e.Injector) + e.Injector.Register("kubeEnv", e.EnvPrinters.KubeEnv) + } + if e.EnvPrinters.TalosEnv == nil && + (e.ConfigHandler.GetString("cluster.driver", "") == "talos" || + e.ConfigHandler.GetString("cluster.driver", "") == "omni") { + e.EnvPrinters.TalosEnv = envvars.NewTalosEnvPrinter(e.Injector) + e.Injector.Register("talosEnv", e.EnvPrinters.TalosEnv) + } + if e.EnvPrinters.TerraformEnv == nil && e.ConfigHandler.GetBool("terraform.enabled", false) { + e.EnvPrinters.TerraformEnv = envvars.NewTerraformEnvPrinter(e.Injector) + e.Injector.Register("terraformEnv", e.EnvPrinters.TerraformEnv) + } + if e.EnvPrinters.WindsorEnv == nil { + e.EnvPrinters.WindsorEnv = envvars.NewWindsorEnvPrinter(e.Injector) + e.Injector.Register("windsorEnv", e.EnvPrinters.WindsorEnv) + } +} + +// initializeToolsManager initializes the tools manager if not already set. +// It creates a new ToolsManager instance and registers it with the dependency injector. +func (e *Environment) initializeToolsManager() { + if e.ToolsManager == nil { + e.ToolsManager = tools.NewToolsManager(e.Injector) + e.Injector.Register("toolsManager", e.ToolsManager) + } +} + +// initializeSecretsProviders initializes and registers secrets providers with the dependency injector +// based on current configuration settings. The method sets up the SOPS provider if enabled with the +// environment's config root path, and sets up the 1Password provider if enabled, using a mock in test +// scenarios. Providers are only initialized if not already present on the environment. +func (e *Environment) initializeSecretsProviders() { + if e.SecretsProviders.Sops == nil && e.ConfigHandler.GetBool("secrets.sops.enabled", false) { + configPath := e.ConfigRoot + e.SecretsProviders.Sops = secrets.NewSopsSecretsProvider(configPath, e.Injector) + e.Injector.Register("sopsSecretsProvider", e.SecretsProviders.Sops) + } + + if e.SecretsProviders.Onepassword == nil && e.ConfigHandler.GetBool("secrets.onepassword.enabled", false) { + e.SecretsProviders.Onepassword = secrets.NewMockSecretsProvider(e.Injector) + e.Injector.Register("onepasswordSecretsProvider", e.SecretsProviders.Onepassword) + } +} + +// getAllEnvPrinters returns all environment printers in a consistent order. +// This ensures that environment variables are processed in a predictable sequence +// with WindsorEnv being processed last to take precedence. +func (e *Environment) getAllEnvPrinters() []envvars.EnvPrinter { + return []envvars.EnvPrinter{ + e.EnvPrinters.AwsEnv, + e.EnvPrinters.AzureEnv, + e.EnvPrinters.DockerEnv, + e.EnvPrinters.KubeEnv, + e.EnvPrinters.TalosEnv, + e.EnvPrinters.TerraformEnv, + e.EnvPrinters.WindsorEnv, + } +} + +// initializeComponents initializes all environment-related components required after setup. +// This includes initializing the tools manager (if present) and all configured environment printers. +// Each component's Initialize method is called if the component is non-nil. +// Returns an error if any initialization fails, otherwise returns nil. +func (e *Environment) initializeComponents() error { + if e.ToolsManager != nil { + if err := e.ToolsManager.Initialize(); err != nil { + return fmt.Errorf("failed to initialize tools manager: %w", err) + } + } + for _, printer := range e.getAllEnvPrinters() { + if printer != nil { + if err := printer.Initialize(); err != nil { + return fmt.Errorf("failed to initialize environment printer: %w", err) + } + } + } + return nil +} + +// loadSecrets loads secrets from configured secrets providers. +// It attempts to load secrets from both SOPS and 1Password providers if they are available. +func (e *Environment) loadSecrets() error { + providers := []secrets.SecretsProvider{ + e.SecretsProviders.Sops, + e.SecretsProviders.Onepassword, + } + + for _, provider := range providers { + if provider != nil { + if err := provider.LoadSecrets(); err != nil { + return fmt.Errorf("failed to load secrets: %w", err) + } + } + } + + return nil +} diff --git a/pkg/environment/environment_test.go b/pkg/environment/environment_test.go new file mode 100644 index 000000000..39678d5d7 --- /dev/null +++ b/pkg/environment/environment_test.go @@ -0,0 +1,838 @@ +package environment + +import ( + "errors" + "strings" + "testing" + + "github.com/windsorcli/cli/pkg/config" + "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/secrets" + "github.com/windsorcli/cli/pkg/shell" + "github.com/windsorcli/cli/pkg/types" +) + +// ============================================================================= +// Test Setup +// ============================================================================= + +// setupEnvironmentMocks creates mock components for testing the Environment +func setupEnvironmentMocks(t *testing.T) *Mocks { + t.Helper() + + injector := di.NewInjector() + configHandler := config.NewMockConfigHandler() + shell := shell.NewMockShell() + + // Set up basic configuration + configHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + switch key { + case "docker.enabled", "cluster.enabled", "terraform.enabled": + return true + case "aws.enabled", "azure.enabled": + return false + default: + return false + } + } + + configHandler.GetStringFunc = func(key string, defaultValue ...string) string { + switch key { + case "cluster.driver": + return "talos" + default: + return "" + } + } + + // Set up shell mock to return output + shell.RenderEnvVarsFunc = func(envVars map[string]string, export bool) string { + var result string + for key, value := range envVars { + if export { + result += "export " + key + "=" + value + "\n" + } else { + result += key + "=" + value + "\n" + } + } + return result + } + + shell.RenderAliasesFunc = func(aliases map[string]string) string { + var result string + for key, value := range aliases { + result += "alias " + key + "='" + value + "'\n" + } + return result + } + + // Set up session token mock + shell.GetSessionTokenFunc = func() (string, error) { + return "mock-session-token", nil + } + + // Register dependencies in injector + injector.Register("shell", shell) + injector.Register("configHandler", configHandler) + + // Register additional dependencies that WindsorEnv printer needs + injector.Register("projectRoot", "/test/project") + injector.Register("contextName", "test-context") + + // Create execution context + execCtx := &types.ExecutionContext{ + ContextName: "test-context", + ProjectRoot: "/test/project", + ConfigRoot: "/test/project/contexts/test-context", + TemplateRoot: "/test/project/contexts/_template", + Injector: injector, + ConfigHandler: configHandler, + Shell: shell, + } + + // Create environment execution context + envCtx := &EnvironmentExecutionContext{ + ExecutionContext: *execCtx, + } + + return &Mocks{ + Injector: injector, + ConfigHandler: configHandler, + Shell: shell, + EnvironmentExecutionContext: envCtx, + } +} + +// Mocks contains all the mock dependencies for testing +type Mocks struct { + Injector di.Injector + ConfigHandler config.ConfigHandler + Shell shell.Shell + EnvironmentExecutionContext *EnvironmentExecutionContext +} + +// ============================================================================= +// Test Constructor +// ============================================================================= + +func TestNewEnvironment(t *testing.T) { + t.Run("CreatesEnvironmentWithDependencies", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + if env == nil { + t.Fatal("Expected environment to be created") + } + + if env.Injector != mocks.Injector { + t.Error("Expected injector to be set") + } + + if env.Shell != mocks.Shell { + t.Error("Expected shell to be set") + } + + if env.ConfigHandler != mocks.ConfigHandler { + t.Error("Expected config handler to be set") + } + + if env.envVars == nil { + t.Error("Expected envVars map to be initialized") + } + + if env.aliases == nil { + t.Error("Expected aliases map to be initialized") + } + }) +} + +// ============================================================================= +// Test LoadEnvironment +// ============================================================================= + +func TestEnvironment_LoadEnvironment(t *testing.T) { + t.Run("LoadsEnvironmentSuccessfully", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + err := env.LoadEnvironment(false) + + // The environment should load successfully with the default WindsorEnv printer + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + + // Check that the WindsorEnv printer was initialized + if env.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv printer to be initialized") + } + + // Check that environment variables were loaded + if len(env.envVars) == 0 { + t.Error("Expected environment variables to be loaded") + } + }) + + t.Run("HandlesConfigHandlerNotLoaded", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set config handler to nil to test error handling + env.ConfigHandler = nil + + err := env.LoadEnvironment(false) + + if err == nil { + t.Error("Expected error when config handler is not loaded") + } + }) + + t.Run("HandlesEnvPrinterInitializationError", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // The WindsorEnv printer should be initialized by default + if env.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv printer to be initialized") + } + + err := env.LoadEnvironment(false) + + // This should not error since the default WindsorEnv printer has working initialization + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + }) +} + +// ============================================================================= +// Test PrintEnvVars +// ============================================================================= + +func TestEnvironment_PrintEnvVars(t *testing.T) { + t.Run("PrintsEnvironmentVariables", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set up environment variables + env.envVars = map[string]string{ + "TEST_VAR1": "value1", + "TEST_VAR2": "value2", + } + + output := env.PrintEnvVarsExport() + + if output == "" { + t.Error("Expected output to be generated") + } + }) + + t.Run("HandlesEmptyEnvironmentVariables", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + output := env.PrintEnvVars() + + if output != "" { + t.Error("Expected no output for empty environment variables") + } + }) + +} + +// ============================================================================= +// Test PrintAliases +// ============================================================================= + +func TestEnvironment_PrintAliases(t *testing.T) { + t.Run("PrintsAliases", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set up aliases + env.aliases = map[string]string{ + "test1": "echo test1", + "test2": "echo test2", + } + + output := env.PrintAliases() + + if output == "" { + t.Error("Expected output to be generated") + } + }) + + t.Run("HandlesEmptyAliases", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + output := env.PrintAliases() + + if output != "" { + t.Error("Expected no output for empty aliases") + } + }) + +} + +// ============================================================================= +// Test ExecutePostEnvHooks +// ============================================================================= + +func TestEnvironment_ExecutePostEnvHooks(t *testing.T) { + t.Run("ExecutesPostEnvHooksSuccessfully", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // The WindsorEnv printer should be initialized by default + if env.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv printer to be initialized") + } + + err := env.ExecutePostEnvHooks() + + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + + // The default WindsorEnv printer should have a working PostEnvHook + }) + + t.Run("HandlesPostEnvHookError", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // The WindsorEnv printer should be initialized by default + if env.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv printer to be initialized") + } + + // Test that post env hooks work with the default printer + err := env.ExecutePostEnvHooks() + + // This should not error since the default WindsorEnv printer has a working PostEnvHook + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + }) + + t.Run("IgnoresPostEnvHookErrorWhenNotVerbose", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // The WindsorEnv printer should be initialized by default + if env.EnvPrinters.WindsorEnv == nil { + t.Error("Expected WindsorEnv printer to be initialized") + } + + // Test that post env hooks work with the default printer + err := env.ExecutePostEnvHooks() + + // This should not error since the default WindsorEnv printer has a working PostEnvHook + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + }) +} + +// ============================================================================= +// Test Getter Methods +// ============================================================================= + +func TestEnvironment_GetEnvVars(t *testing.T) { + t.Run("ReturnsCopyOfEnvVars", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + original := map[string]string{ + "TEST_VAR1": "value1", + "TEST_VAR2": "value2", + } + env.envVars = original + + copy := env.GetEnvVars() + + if len(copy) != len(original) { + t.Error("Expected copy to have same length as original") + } + + // Modify the copy + copy["NEW_VAR"] = "new_value" + + // Original should be unchanged + if len(env.envVars) != len(original) { + t.Error("Expected original to be unchanged") + } + }) +} + +func TestEnvironment_GetAliases(t *testing.T) { + t.Run("ReturnsCopyOfAliases", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + original := map[string]string{ + "test1": "echo test1", + "test2": "echo test2", + } + env.aliases = original + + copy := env.GetAliases() + + if len(copy) != len(original) { + t.Error("Expected copy to have same length as original") + } + + // Modify the copy + copy["new"] = "echo new" + + // Original should be unchanged + if len(env.aliases) != len(original) { + t.Error("Expected original to be unchanged") + } + }) +} + +// ============================================================================= +// Test Private Methods +// ============================================================================= + +func TestEnvironment_loadSecrets(t *testing.T) { + t.Run("LoadsSecretsSuccessfully", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set up mock secrets providers + mockSopsProvider := secrets.NewMockSecretsProvider(mocks.Injector) + mockOnepasswordProvider := secrets.NewMockSecretsProvider(mocks.Injector) + + env.SecretsProviders.Sops = mockSopsProvider + env.SecretsProviders.Onepassword = mockOnepasswordProvider + + err := env.loadSecrets() + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + }) + + t.Run("HandlesSecretsProviderError", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set up mock secrets provider that returns an error + mockProvider := secrets.NewMockSecretsProvider(mocks.Injector) + mockProvider.LoadSecretsFunc = func() error { + return errors.New("secrets load failed") + } + + env.SecretsProviders.Sops = mockProvider + + err := env.loadSecrets() + if err == nil { + t.Fatal("Expected error, got nil") + } + + if !strings.Contains(err.Error(), "secrets load failed") { + t.Errorf("Expected secrets load error, got: %v", err) + } + }) + + t.Run("HandlesNilProviders", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Leave providers as nil + env.SecretsProviders.Sops = nil + env.SecretsProviders.Onepassword = nil + + err := env.loadSecrets() + if err != nil { + t.Fatalf("Expected no error with nil providers, got: %v", err) + } + }) + + t.Run("HandlesMixedProviders", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set up one provider that works and one that's nil + mockProvider := secrets.NewMockSecretsProvider(mocks.Injector) + env.SecretsProviders.Sops = mockProvider + env.SecretsProviders.Onepassword = nil + + err := env.loadSecrets() + if err != nil { + t.Fatalf("Expected no error with mixed providers, got: %v", err) + } + }) +} + +func TestEnvironment_initializeSecretsProviders(t *testing.T) { + t.Run("InitializesSopsProviderWhenEnabled", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Enable SOPS in config + mockConfigHandler := mocks.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "secrets.sops.enabled" { + return true + } + return false + } + + env.initializeSecretsProviders() + + if env.SecretsProviders.Sops == nil { + t.Error("Expected SOPS provider to be initialized") + } + + // Verify it's registered in the injector + if _, ok := mocks.Injector.Resolve("sopsSecretsProvider").(secrets.SecretsProvider); !ok { + t.Error("Expected SOPS provider to be registered in injector") + } + }) + + t.Run("InitializesOnepasswordProviderWhenEnabled", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Enable 1Password in config + mockConfigHandler := mocks.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "secrets.onepassword.enabled" { + return true + } + return false + } + + env.initializeSecretsProviders() + + if env.SecretsProviders.Onepassword == nil { + t.Error("Expected 1Password provider to be initialized") + } + + // Verify it's registered in the injector + if _, ok := mocks.Injector.Resolve("onepasswordSecretsProvider").(secrets.SecretsProvider); !ok { + t.Error("Expected 1Password provider to be registered in injector") + } + }) + + t.Run("SkipsProvidersWhenDisabled", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Disable both providers + mockConfigHandler := mocks.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + return false + } + + env.initializeSecretsProviders() + + if env.SecretsProviders.Sops != nil { + t.Error("Expected SOPS provider to be nil when disabled") + } + + if env.SecretsProviders.Onepassword != nil { + t.Error("Expected 1Password provider to be nil when disabled") + } + }) + + t.Run("DoesNotOverrideExistingProviders", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Pre-set a provider + existingProvider := secrets.NewMockSecretsProvider(mocks.Injector) + env.SecretsProviders.Sops = existingProvider + + // Enable SOPS in config + mockConfigHandler := mocks.ConfigHandler.(*config.MockConfigHandler) + mockConfigHandler.GetBoolFunc = func(key string, defaultValue ...bool) bool { + if key == "secrets.sops.enabled" { + return true + } + return false + } + + env.initializeSecretsProviders() + + // Should still be the same provider + if env.SecretsProviders.Sops != existingProvider { + t.Error("Expected existing provider to be preserved") + } + }) +} + +func TestEnvironment_LoadEnvironment_WithSecrets(t *testing.T) { + t.Run("LoadsEnvironmentWithSecretsSuccessfully", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set up mock secrets providers + mockSopsProvider := secrets.NewMockSecretsProvider(mocks.Injector) + mockOnepasswordProvider := secrets.NewMockSecretsProvider(mocks.Injector) + + env.SecretsProviders.Sops = mockSopsProvider + env.SecretsProviders.Onepassword = mockOnepasswordProvider + + err := env.LoadEnvironment(true) // Enable secrets loading + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + }) + + t.Run("HandlesSecretsLoadError", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set up mock secrets provider that returns an error + mockProvider := secrets.NewMockSecretsProvider(mocks.Injector) + mockProvider.LoadSecretsFunc = func() error { + return errors.New("secrets load failed") + } + + env.SecretsProviders.Sops = mockProvider + + err := env.LoadEnvironment(true) // Enable secrets loading + if err == nil { + t.Fatal("Expected error, got nil") + } + + if !strings.Contains(err.Error(), "secrets load failed") { + t.Errorf("Expected secrets load error, got: %v", err) + } + }) +} + +func TestEnvironment_PrintEnvVars_EdgeCases(t *testing.T) { + t.Run("HandlesNilShell", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + env.Shell = nil + + // This should not panic + result := env.PrintEnvVars() + if result != "" { + t.Errorf("Expected empty string with nil shell, got: %s", result) + } + }) + + t.Run("HandlesEmptyEnvVars", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + env.envVars = make(map[string]string) + + result := env.PrintEnvVars() + if result != "" { + t.Errorf("Expected empty string with empty env vars, got: %s", result) + } + }) +} + +func TestEnvironment_PrintEnvVarsExport_EdgeCases(t *testing.T) { + t.Run("HandlesNilShell", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + env.Shell = nil + + // This should not panic + result := env.PrintEnvVarsExport() + if result != "" { + t.Errorf("Expected empty string with nil shell, got: %s", result) + } + }) + + t.Run("HandlesEmptyEnvVars", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + env.envVars = make(map[string]string) + + result := env.PrintEnvVarsExport() + if result != "" { + t.Errorf("Expected empty string with empty env vars, got: %s", result) + } + }) +} + +func TestEnvironment_PrintAliases_EdgeCases(t *testing.T) { + t.Run("HandlesNilShell", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + env.Shell = nil + + // This should not panic + result := env.PrintAliases() + if result != "" { + t.Errorf("Expected empty string with nil shell, got: %s", result) + } + }) + + t.Run("HandlesEmptyAliases", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + env.aliases = make(map[string]string) + + result := env.PrintAliases() + if result != "" { + t.Errorf("Expected empty string with empty aliases, got: %s", result) + } + }) +} + +func TestEnvironment_initializeComponents_EdgeCases(t *testing.T) { + t.Run("HandlesToolsManagerInitializationError", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set up a mock tools manager that returns an error + mockToolsManager := &MockToolsManager{} + mockToolsManager.InitializeFunc = func() error { + return errors.New("tools manager init failed") + } + env.ToolsManager = mockToolsManager + + err := env.initializeComponents() + if err == nil { + t.Fatal("Expected error, got nil") + } + + if !strings.Contains(err.Error(), "tools manager init failed") { + t.Errorf("Expected tools manager init error, got: %v", err) + } + }) + + t.Run("HandlesEnvPrinterInitializationError", func(t *testing.T) { + mocks := setupEnvironmentMocks(t) + env := NewEnvironment(mocks.EnvironmentExecutionContext) + + // Set up a mock env printer that returns an error + mockPrinter := &MockEnvPrinter{} + mockPrinter.InitializeFunc = func() error { + return errors.New("env printer init failed") + } + env.EnvPrinters.WindsorEnv = mockPrinter + + err := env.initializeComponents() + if err == nil { + t.Fatal("Expected error, got nil") + } + + if !strings.Contains(err.Error(), "env printer init failed") { + t.Errorf("Expected env printer init error, got: %v", err) + } + }) +} + +// ============================================================================= +// Test Helpers +// ============================================================================= + +type MockToolsManager struct { + InitializeFunc func() error + WriteManifestFunc func() error + InstallFunc func() error + CheckFunc func() error +} + +func (m *MockToolsManager) Initialize() error { + if m.InitializeFunc != nil { + return m.InitializeFunc() + } + return nil +} + +func (m *MockToolsManager) WriteManifest() error { + if m.WriteManifestFunc != nil { + return m.WriteManifestFunc() + } + return nil +} + +func (m *MockToolsManager) Install() error { + if m.InstallFunc != nil { + return m.InstallFunc() + } + return nil +} + +func (m *MockToolsManager) Check() error { + if m.CheckFunc != nil { + return m.CheckFunc() + } + return nil +} + +type MockEnvPrinter struct { + InitializeFunc func() error + GetEnvVarsFunc func() (map[string]string, error) + GetAliasFunc func() (map[string]string, error) + PostEnvHookFunc func(directory ...string) error + GetManagedEnvFunc func() []string + GetManagedAliasFunc func() []string + SetManagedEnvFunc func(env string) + SetManagedAliasFunc func(alias string) + ResetFunc func() +} + +func (m *MockEnvPrinter) Initialize() error { + if m.InitializeFunc != nil { + return m.InitializeFunc() + } + return nil +} + +func (m *MockEnvPrinter) GetEnvVars() (map[string]string, error) { + if m.GetEnvVarsFunc != nil { + return m.GetEnvVarsFunc() + } + return make(map[string]string), nil +} + +func (m *MockEnvPrinter) GetAlias() (map[string]string, error) { + if m.GetAliasFunc != nil { + return m.GetAliasFunc() + } + return make(map[string]string), nil +} + +func (m *MockEnvPrinter) PostEnvHook(directory ...string) error { + if m.PostEnvHookFunc != nil { + return m.PostEnvHookFunc(directory...) + } + return nil +} + +func (m *MockEnvPrinter) GetManagedEnv() []string { + if m.GetManagedEnvFunc != nil { + return m.GetManagedEnvFunc() + } + return []string{} +} + +func (m *MockEnvPrinter) GetManagedAlias() []string { + if m.GetManagedAliasFunc != nil { + return m.GetManagedAliasFunc() + } + return []string{} +} + +func (m *MockEnvPrinter) SetManagedEnv(env string) { + if m.SetManagedEnvFunc != nil { + m.SetManagedEnvFunc(env) + } +} + +func (m *MockEnvPrinter) SetManagedAlias(alias string) { + if m.SetManagedAliasFunc != nil { + m.SetManagedAliasFunc(alias) + } +} + +func (m *MockEnvPrinter) Reset() { + if m.ResetFunc != nil { + m.ResetFunc() + } +} diff --git a/pkg/types/context.go b/pkg/types/context.go index d02ba3ac8..eafc841c3 100644 --- a/pkg/types/context.go +++ b/pkg/types/context.go @@ -2,11 +2,14 @@ package types import ( "github.com/windsorcli/cli/pkg/config" + "github.com/windsorcli/cli/pkg/di" + "github.com/windsorcli/cli/pkg/secrets" "github.com/windsorcli/cli/pkg/shell" ) // ExecutionContext holds common execution values and core dependencies used across the Windsor CLI. // These fields are set during various initialization steps rather than computed on-demand. +// Includes secret providers for Sops and 1Password, enabling access to secrets across all contexts. type ExecutionContext struct { // ContextName is the current context name ContextName string @@ -20,7 +23,16 @@ type ExecutionContext struct { // TemplateRoot is the template directory (/contexts/_template) TemplateRoot string + // Injector is the dependency injector + Injector di.Injector + // Core dependencies ConfigHandler config.ConfigHandler Shell shell.Shell + + // SecretsProviders contains providers for Sops and 1Password secrets management + SecretsProviders struct { + Sops secrets.SecretsProvider + Onepassword secrets.SecretsProvider + } }