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
53 changes: 23 additions & 30 deletions cmd/install.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package cmd

import (
"context"
"fmt"

"github.com/spf13/cobra"
"github.com/windsorcli/cli/pkg/di"
"github.com/windsorcli/cli/pkg/pipelines"
"github.com/windsorcli/cli/pkg/runtime"
"github.com/windsorcli/cli/pkg/project"
)

var installWaitFlag bool
Expand All @@ -17,42 +15,37 @@ var installCmd = &cobra.Command{
Short: "Install the blueprint's cluster-level services",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
// Get shared dependency injector from context
injector := cmd.Context().Value(injectorKey).(di.Injector)

// First, set up environment variables using runtime
deps := &runtime.Dependencies{
Injector: injector,
proj, err := project.NewProject(injector, "")
if err != nil {
return err
}
if err := runtime.NewRuntime(deps).
LoadShell().
CheckTrustedDirectory().
LoadConfig().
LoadSecretsProviders().
LoadEnvVars(runtime.EnvVarsOptions{
Decrypt: true,
Verbose: verbose,
}).
ExecutePostEnvHook(verbose).
Do(); err != nil {
return fmt.Errorf("failed to set up environment: %w", err)

if err := proj.Context.Shell.CheckTrustedDirectory(); err != nil {
return fmt.Errorf("not in a trusted directory. If you are in a Windsor project, run 'windsor init' to approve")
}

// Then, run the install pipeline for blueprint installation
installPipeline, err := pipelines.WithPipeline(injector, cmd.Context(), "installPipeline")
if err != nil {
return fmt.Errorf("failed to set up install pipeline: %w", err)
if err := proj.Configure(nil); err != nil {
return err
}

// Create execution context with flags
ctx := cmd.Context()
if installWaitFlag {
ctx = context.WithValue(ctx, "wait", true)
if err := proj.Initialize(false); err != nil {
if !verbose {
return nil
}
return err
}

// Execute the install pipeline
if err := installPipeline.Execute(ctx); err != nil {
return fmt.Errorf("Error executing install pipeline: %w", err)
blueprint := proj.Composer.BlueprintHandler.Generate()
if err := proj.Provisioner.Install(blueprint); err != nil {
return fmt.Errorf("error installing blueprint: %w", err)
}

if installWaitFlag {
if err := proj.Provisioner.Wait(blueprint); err != nil {
return fmt.Errorf("error waiting for kustomizations: %w", err)
}
}

return nil
Expand Down
128 changes: 58 additions & 70 deletions cmd/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,141 +8,129 @@ import (

"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/windsorcli/cli/pkg/di"
"github.com/windsorcli/cli/pkg/pipelines"
blueprintv1alpha1 "github.com/windsorcli/cli/api/v1alpha1"
"github.com/windsorcli/cli/pkg/composer/blueprint"
"github.com/windsorcli/cli/pkg/context/config"
terraforminfra "github.com/windsorcli/cli/pkg/provisioner/terraform"
"github.com/windsorcli/cli/pkg/workstation/virt"
)

// =============================================================================
// Test Setup
// =============================================================================

type InstallMocks struct {
*Mocks
}

func setupInstallTest(t *testing.T, opts ...*SetupOptions) *InstallMocks {
t.Helper()

// Setup base mocks
baseMocks := setupMocks(t, opts...)

// Note: envPipeline no longer used - install now uses runtime.LoadEnvVars

// Register mock install pipeline in injector
mockInstallPipeline := pipelines.NewMockBasePipeline()
mockInstallPipeline.InitializeFunc = func(injector di.Injector, ctx context.Context) error { return nil }
mockInstallPipeline.ExecuteFunc = func(ctx context.Context) error { return nil }
baseMocks.Injector.Register("installPipeline", mockInstallPipeline)

return &InstallMocks{
Mocks: baseMocks,
}
}

// =============================================================================
// Test Cases
// =============================================================================

func TestInstallCmd(t *testing.T) {
createTestInstallCmd := func() *cobra.Command {
// Create a new command with the same RunE as installCmd
cmd := &cobra.Command{
Use: "install",
Short: "Install the blueprint's cluster-level services",
RunE: installCmd.RunE,
}

// Copy all flags from installCmd to ensure they're available
installCmd.Flags().VisitAll(func(flag *pflag.Flag) {
cmd.Flags().AddFlag(flag)
})

// Disable help text printing
cmd.SilenceUsage = true
cmd.SilenceErrors = true

return cmd
}

t.Run("Success", func(t *testing.T) {
// Given a temporary directory with mocked dependencies
mocks := setupInstallTest(t)
mocks := setupMocks(t)

mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector)
mockBlueprintHandler.GenerateFunc = func() *blueprintv1alpha1.Blueprint {
return &blueprintv1alpha1.Blueprint{}
}
mocks.Injector.Register("blueprintHandler", mockBlueprintHandler)

mockStack := &terraforminfra.MockStack{}
mockStack.InitializeFunc = func() error { return nil }
mocks.Injector.Register("stack", mockStack)

mockContainerRuntime := &virt.MockVirt{}
mockContainerRuntime.InitializeFunc = func() error { return nil }
mocks.Injector.Register("containerRuntime", mockContainerRuntime)

// When executing the install command
cmd := createTestInstallCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
cmd.SetArgs([]string{})
cmd.SetContext(ctx)
err := cmd.Execute()

// Then no error should occur
if err != nil {
t.Errorf("Expected success, got error: %v", err)
}
})

t.Run("SuccessWithWaitFlag", func(t *testing.T) {
// Given a temporary directory with mocked dependencies
mocks := setupInstallTest(t)
mocks := setupMocks(t)

mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector)
mockBlueprintHandler.GenerateFunc = func() *blueprintv1alpha1.Blueprint {
return &blueprintv1alpha1.Blueprint{}
}
mocks.Injector.Register("blueprintHandler", mockBlueprintHandler)

mockStack := &terraforminfra.MockStack{}
mockStack.InitializeFunc = func() error { return nil }
mocks.Injector.Register("stack", mockStack)

mockContainerRuntime := &virt.MockVirt{}
mockContainerRuntime.InitializeFunc = func() error { return nil }
mocks.Injector.Register("containerRuntime", mockContainerRuntime)

// When executing the install command with wait flag
cmd := createTestInstallCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
cmd.SetArgs([]string{"--wait"})
cmd.SetContext(ctx)
err := cmd.Execute()

// Then no error should occur
if err != nil {
t.Errorf("Expected success, got error: %v", err)
}
})

t.Run("SuccessWithVerboseContext", func(t *testing.T) {
// Given a temporary directory with mocked dependencies
mocks := setupInstallTest(t)
t.Run("ErrorCheckingTrustedDirectory", func(t *testing.T) {
mocks := setupMocks(t)

mockShell := mocks.Shell
mockShell.CheckTrustedDirectoryFunc = func() error {
return fmt.Errorf("not in trusted directory")
}

// When executing the install command with verbose context
cmd := createTestInstallCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
ctx = context.WithValue(ctx, "verbose", true)
cmd.SetArgs([]string{})
cmd.SetContext(ctx)
err := cmd.Execute()

// Then no error should occur
if err != nil {
t.Errorf("Expected success, got error: %v", err)
if err == nil {
t.Error("Expected error, got nil")
}
if !strings.Contains(err.Error(), "not in a trusted directory") {
t.Errorf("Expected trusted directory error, got: %v", err)
}
})

// Note: ReturnsErrorWhenEnvPipelineSetupFails test removed - env pipeline no longer used

t.Run("ReturnsErrorWhenInstallPipelineSetupFails", func(t *testing.T) {
// Given a temporary directory with mocked dependencies
mocks := setupInstallTest(t)
t.Run("ErrorLoadingConfig", func(t *testing.T) {
mockConfigHandler := config.NewMockConfigHandler()
mockConfigHandler.LoadConfigFunc = func() error {
return fmt.Errorf("config load failed")
}

// Override install pipeline to return error during execution
mockInstallPipeline := pipelines.NewMockBasePipeline()
mockInstallPipeline.InitializeFunc = func(injector di.Injector, ctx context.Context) error { return nil }
mockInstallPipeline.ExecuteFunc = func(ctx context.Context) error {
return fmt.Errorf("install pipeline execution failed")
opts := &SetupOptions{
ConfigHandler: mockConfigHandler,
}
mocks.Injector.Register("installPipeline", mockInstallPipeline)
mocks := setupMocks(t, opts)

// When executing the install command
cmd := createTestInstallCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
cmd.SetArgs([]string{})
cmd.SetContext(ctx)
err := cmd.Execute()

// Then an error should be returned
if err == nil {
t.Fatal("Expected error, got nil")
t.Error("Expected error, got nil")
}
if !strings.Contains(err.Error(), "Error executing install pipeline") {
t.Errorf("Expected install pipeline execution error, got %q", err.Error())
if !strings.Contains(err.Error(), "failed to load config") {
t.Errorf("Expected config load error, got: %v", err)
}
})
}
21 changes: 17 additions & 4 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import (
"testing"

"github.com/spf13/cobra"
blueprintpkg "github.com/windsorcli/cli/pkg/composer/blueprint"
"github.com/windsorcli/cli/pkg/context/config"
"github.com/windsorcli/cli/pkg/di"
envvars "github.com/windsorcli/cli/pkg/context/env"
"github.com/windsorcli/cli/pkg/provisioner/kubernetes"
blueprintpkg "github.com/windsorcli/cli/pkg/composer/blueprint"
"github.com/windsorcli/cli/pkg/context/secrets"
"github.com/windsorcli/cli/pkg/context/shell"
"github.com/windsorcli/cli/pkg/di"
"github.com/windsorcli/cli/pkg/provisioner/kubernetes"
)

// =============================================================================
Expand All @@ -34,6 +34,7 @@ type Mocks struct {
EnvPrinter *envvars.MockEnvPrinter
Shims *Shims
BlueprintHandler *blueprintpkg.MockBlueprintHandler
TmpDir string
}

type SetupOptions struct {
Expand Down Expand Up @@ -82,6 +83,9 @@ func setupMocks(t *testing.T, opts ...*SetupOptions) *Mocks {
// Set global shims
shims = testShims

// Create temporary directory for test
tmpDir := t.TempDir()

// Create injector
var injector di.Injector
if options.Injector == nil {
Expand All @@ -93,7 +97,7 @@ func setupMocks(t *testing.T, opts ...*SetupOptions) *Mocks {
// Create and register mock shell
mockShell := shell.NewMockShell()
mockShell.GetProjectRootFunc = func() (string, error) {
return t.TempDir(), nil
return tmpDir, nil
}
mockShell.CheckTrustedDirectoryFunc = func() error {
return nil
Expand Down Expand Up @@ -146,6 +150,14 @@ func setupMocks(t *testing.T, opts ...*SetupOptions) *Mocks {
configHandler = config.NewConfigHandler(injector)
} else {
configHandler = options.ConfigHandler
// If it's a mock config handler, set GetConfigRootFunc to use tmpDir
if mockConfig, ok := configHandler.(*config.MockConfigHandler); ok {
if mockConfig.GetConfigRootFunc == nil {
mockConfig.GetConfigRootFunc = func() (string, error) {
return tmpDir, nil
}
}
}
}

// Register config handler
Expand Down Expand Up @@ -188,6 +200,7 @@ func setupMocks(t *testing.T, opts ...*SetupOptions) *Mocks {
EnvPrinter: mockEnvPrinter,
Shims: testShims,
BlueprintHandler: mockBlueprintHandler,
TmpDir: tmpDir,
}
}

Expand Down
Loading