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
45 changes: 21 additions & 24 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/spf13/cobra"
"github.com/windsorcli/cli/pkg/config"
"github.com/windsorcli/cli/pkg/constants"
"github.com/windsorcli/cli/pkg/di"
"github.com/windsorcli/cli/pkg/pipelines"
)
Expand Down Expand Up @@ -53,22 +52,6 @@ var initCmd = &cobra.Command{
initProvider = initPlatform
}

// If context is "local" and neither provider nor blueprint is set, set both
if len(args) > 0 && strings.HasPrefix(args[0], "local") && initProvider == "" && initBlueprint == "" {
initProvider = "local"
initBlueprint = constants.DEFAULT_OCI_BLUEPRINT_URL
}

// If provider is set and blueprint is not set, set blueprint (covers all providers, including local)
if initProvider != "" && initBlueprint == "" {
initBlueprint = constants.DEFAULT_OCI_BLUEPRINT_URL
}

// If blueprint is set, use it (overrides all)
if initBlueprint != "" {
ctx = context.WithValue(ctx, "blueprint", initBlueprint)
}

ctx = context.WithValue(ctx, "quiet", true)
ctx = context.WithValue(ctx, "decrypt", true)
envPipeline, err := pipelines.WithPipeline(injector, ctx, "envPipeline")
Expand All @@ -79,8 +62,29 @@ var initCmd = &cobra.Command{
return fmt.Errorf("failed to set up environment: %w", err)
}

// Set provider if context is "local" and no provider is specified
if len(args) > 0 && strings.HasPrefix(args[0], "local") && initProvider == "" {
initProvider = "local"
}

// Pass blueprint and provider to pipeline for decision logic
if initBlueprint != "" {
ctx = context.WithValue(ctx, "blueprint", initBlueprint)
}
if initProvider != "" {
ctx = context.WithValue(ctx, "provider", initProvider)
}

configHandler := injector.Resolve("configHandler").(config.ConfigHandler)

// Set provider in context if it's been set (either via --provider or --platform)
if initProvider != "" {
if err := configHandler.SetContextValue("provider", initProvider); err != nil {
return fmt.Errorf("failed to set provider: %w", err)
}
}

// Set other configuration values
if initBackend != "" {
if err := configHandler.SetContextValue("terraform.backend.type", initBackend); err != nil {
return fmt.Errorf("failed to set terraform.backend.type: %w", err)
Expand Down Expand Up @@ -132,13 +136,6 @@ var initCmd = &cobra.Command{
}
}

// Set provider in context if it's been set (either via --provider or --platform)
if initProvider != "" {
if err := configHandler.SetContextValue("provider", initProvider); err != nil {
return fmt.Errorf("failed to set provider: %w", err)
}
}

for _, setFlag := range initSetFlags {
parts := strings.SplitN(setFlag, "=", 2)
if len(parts) == 2 {
Expand Down
106 changes: 104 additions & 2 deletions cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -935,9 +935,111 @@ func TestInitCmd(t *testing.T) {
cmd.SetContext(ctx)
err := cmd.Execute()

// Then no error should occur - platform should override auto-set provider
// Then no error should occur and platform should override auto-set provider
if err != nil {
t.Errorf("Expected success when platform overrides auto-set provider, got error: %v", err)
t.Errorf("Expected success, got error: %v", err)
}
})

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

// When executing the init command with context name that matches a provider
cmd := createTestInitCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
cmd.SetArgs([]string{"aws"}) // No explicit provider flag
cmd.SetContext(ctx)
err := cmd.Execute()

// Then no error should occur and context name should be used as provider
if err != nil {
t.Errorf("Expected success, got error: %v", err)
}
})

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

// When executing the init command with "azure" context name
cmd := createTestInitCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
cmd.SetArgs([]string{"azure"}) // No explicit provider flag
cmd.SetContext(ctx)
err := cmd.Execute()

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

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

// When executing the init command with "metal" context name
cmd := createTestInitCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
cmd.SetArgs([]string{"metal"}) // No explicit provider flag
cmd.SetContext(ctx)
err := cmd.Execute()

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

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

// When executing the init command with "local" context name
cmd := createTestInitCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
cmd.SetArgs([]string{"local"}) // No explicit provider flag
cmd.SetContext(ctx)
err := cmd.Execute()

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

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

// When executing the init command with explicit provider that differs from context name
cmd := createTestInitCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
cmd.SetArgs([]string{"aws", "--provider", "azure"}) // Context name vs explicit provider
cmd.SetContext(ctx)
err := cmd.Execute()

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

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

// When executing the init command with unknown context name
cmd := createTestInitCmd()
ctx := context.WithValue(context.Background(), injectorKey, mocks.Injector)
cmd.SetArgs([]string{"unknown-context"}) // Unknown context name
cmd.SetContext(ctx)
err := cmd.Execute()

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

Expand Down
6 changes: 3 additions & 3 deletions pkg/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ import (
// DefaultConfig returns the default configuration
var DefaultConfig = v1alpha1.Context{
Provider: ptrString("local"),
Cluster: &cluster.ClusterConfig{
Enabled: ptrBool(true),
},
Terraform: &terraform.TerraformConfig{
Enabled: ptrBool(true),
Backend: &terraform.BackendConfig{
Type: "local",
},
},
Cluster: &cluster.ClusterConfig{
Enabled: ptrBool(true),
},
}

var commonDockerConfig = docker.DockerConfig{
Expand Down
24 changes: 16 additions & 8 deletions pkg/config/yaml_config_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,30 @@ func NewYamlConfigHandler(injector di.Injector) *YamlConfigHandler {
// Public Methods
// =============================================================================

// LoadConfigString loads configuration from the provided YAML string content.
// It unmarshals the YAML into the internal config structure, tracks root contexts,
// validates and sets the config version, and marks the configuration as loaded.
// Returns an error if unmarshalling fails or if the config version is unsupported.
// LoadConfigString loads configuration from a YAML string into the internal config structure.
// It unmarshals the YAML, records which contexts were present in the input, validates and sets
// the config version, and marks the configuration as loaded. Returns an error if unmarshalling
// fails or if the config version is unsupported.
func (y *YamlConfigHandler) LoadConfigString(content string) error {
if content == "" {
return nil
}

if err := y.shims.YamlUnmarshal([]byte(content), &y.BaseConfigHandler.config); err != nil {
var tempConfig v1alpha1.Config
if err := y.shims.YamlUnmarshal([]byte(content), &tempConfig); err != nil {
return fmt.Errorf("error unmarshalling yaml: %w", err)
}

if y.config.Contexts != nil {
for contextName := range y.config.Contexts {
if tempConfig.Contexts != nil {
for contextName := range tempConfig.Contexts {
y.rootContexts[contextName] = true
}
}

if err := y.shims.YamlUnmarshal([]byte(content), &y.BaseConfigHandler.config); err != nil {
return fmt.Errorf("error unmarshalling yaml: %w", err)
}

if y.BaseConfigHandler.config.Version == "" {
y.BaseConfigHandler.config.Version = "v1alpha1"
} else if y.BaseConfigHandler.config.Version != "v1alpha1" {
Expand Down Expand Up @@ -199,6 +204,7 @@ func (y *YamlConfigHandler) SaveConfig(overwrite ...bool) error {

if shouldCreateContextConfig {
var contextConfig v1alpha1.Context

if y.config.Contexts != nil && y.config.Contexts[contextName] != nil {
contextConfig = *y.config.Contexts[contextName]
} else {
Expand Down Expand Up @@ -244,9 +250,11 @@ func (y *YamlConfigHandler) SetDefault(context v1alpha1.Context) error {
y.config.Contexts[currentContext] = &v1alpha1.Context{}
}

// Merge existing values INTO defaults (not the other way around)
// This ensures that existing explicit settings take precedence over defaults
defaultCopy := context.DeepCopy()
existingCopy := y.config.Contexts[currentContext].DeepCopy()
defaultCopy.Merge(existingCopy)
defaultCopy.Merge(existingCopy) // Merge existing INTO defaults
y.config.Contexts[currentContext] = defaultCopy
}

Expand Down
Loading
Loading