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
20 changes: 19 additions & 1 deletion api/v1alpha1/blueprint_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ func (b *Blueprint) strategicMergeTerraformComponent(component TerraformComponen
if existing.Inputs == nil {
existing.Inputs = make(map[string]any)
}
maps.Copy(existing.Inputs, component.Inputs)
existing.Inputs = b.deepMergeMaps(existing.Inputs, component.Inputs)
}
for _, dep := range component.DependsOn {
if !slices.Contains(existing.DependsOn, dep) {
Expand Down Expand Up @@ -872,3 +872,21 @@ func (b *Blueprint) terraformTopologicalSort(pathToIndex map[string]int) []int {

return sorted
}

// deepMergeMaps returns a new map from a deep merge of base and overlay maps.
// Overlay values take precedence; nested maps merge recursively. Non-map overlay values replace base values.
func (b *Blueprint) deepMergeMaps(base, overlay map[string]any) map[string]any {
result := maps.Clone(base)
for k, overlayValue := range overlay {
if baseValue, exists := result[k]; exists {
if baseMap, baseIsMap := baseValue.(map[string]any); baseIsMap {
if overlayMap, overlayIsMap := overlayValue.(map[string]any); overlayIsMap {
result[k] = b.deepMergeMaps(baseMap, overlayMap)
continue
}
}
}
result[k] = overlayValue
}
return result
}
34 changes: 33 additions & 1 deletion cmd/down_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import (

"github.com/spf13/cobra"
"github.com/spf13/pflag"
blueprintv1alpha1 "github.com/windsorcli/cli/api/v1alpha1"
"github.com/windsorcli/cli/pkg/composer"
"github.com/windsorcli/cli/pkg/composer/blueprint"
"github.com/windsorcli/cli/pkg/project"
"github.com/windsorcli/cli/pkg/provisioner"
"github.com/windsorcli/cli/pkg/provisioner/kubernetes"
terraforminfra "github.com/windsorcli/cli/pkg/provisioner/terraform"
"github.com/windsorcli/cli/pkg/runtime/config"
)

Expand All @@ -27,7 +33,33 @@ func setupDownTest(t *testing.T, opts ...*SetupOptions) *DownMocks {

baseMocks := setupMocks(t, opts...)

proj, err := project.NewProject("", &project.Project{Runtime: baseMocks.Runtime})
mockBlueprintHandler := blueprint.NewMockBlueprintHandler()
mockBlueprintHandler.GenerateFunc = func() *blueprintv1alpha1.Blueprint {
return &blueprintv1alpha1.Blueprint{}
}

mockKubernetesManager := kubernetes.NewMockKubernetesManager()
mockKubernetesManager.DeleteBlueprintFunc = func(blueprint *blueprintv1alpha1.Blueprint, namespace string) error {
return nil
}

mockTerraformStack := terraforminfra.NewMockStack()
mockTerraformStack.DownFunc = func(blueprint *blueprintv1alpha1.Blueprint) error {
return nil
}

comp := composer.NewComposer(baseMocks.Runtime)
comp.BlueprintHandler = mockBlueprintHandler
mockProvisioner := provisioner.NewProvisioner(baseMocks.Runtime, comp.BlueprintHandler, &provisioner.Provisioner{
TerraformStack: mockTerraformStack,
KubernetesManager: mockKubernetesManager,
})

proj, err := project.NewProject("", &project.Project{
Runtime: baseMocks.Runtime,
Composer: comp,
Provisioner: mockProvisioner,
})
if err != nil {
t.Fatalf("Failed to create project: %v", err)
}
Expand Down
24 changes: 20 additions & 4 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/spf13/cobra"
"github.com/windsorcli/cli/pkg/composer"
"github.com/windsorcli/cli/pkg/project"
"github.com/windsorcli/cli/pkg/runtime"
)
Expand Down Expand Up @@ -112,9 +113,20 @@ var initCmd = &cobra.Command{
}
}

proj, err := project.NewProject(contextName, &project.Project{
Runtime: rt,
})
var projectOpts *project.Project
if composerOverrideVal := cmd.Context().Value(composerOverridesKey); composerOverrideVal != nil {
compOverride := composerOverrideVal.(*composer.Composer)
projectOpts = &project.Project{
Runtime: rt,
Composer: compOverride,
}
} else {
projectOpts = &project.Project{
Runtime: rt,
}
}

proj, err := project.NewProject(contextName, projectOpts)
if err != nil {
return err
}
Expand All @@ -127,7 +139,11 @@ var initCmd = &cobra.Command{
return fmt.Errorf("failed to handle session reset: %w", err)
}

if err := proj.Initialize(initReset); err != nil {
var blueprintURL []string
if initBlueprint != "" {
blueprintURL = []string{initBlueprint}
}
if err := proj.Initialize(initReset, blueprintURL...); err != nil {
return err
}

Expand Down
28 changes: 24 additions & 4 deletions cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/windsorcli/cli/pkg/composer"
"github.com/windsorcli/cli/pkg/composer/blueprint"
"github.com/windsorcli/cli/pkg/constants"
"github.com/windsorcli/cli/pkg/runtime"
Expand Down Expand Up @@ -58,10 +59,11 @@ func setupInitTest(t *testing.T, opts ...*SetupOptions) *InitMocks {

// Add blueprint handler mock
mockBlueprintHandler := blueprint.NewMockBlueprintHandler()
mockBlueprintHandler.LoadBlueprintFunc = func() error { return nil }
mockBlueprintHandler.LoadBlueprintFunc = func(...string) error { return nil }
mockBlueprintHandler.WriteFunc = func(overwrite ...bool) error { return nil }
// Configure tools manager (required by runInit)
// Configure tools manager (required by runInit and PrepareTools)
baseMocks.ToolsManager.InstallFunc = func() error { return nil }
baseMocks.ToolsManager.CheckFunc = func() error { return nil }

return &InitMocks{
ConfigHandler: baseMocks.ConfigHandler,
Expand Down Expand Up @@ -330,10 +332,16 @@ func TestInitCmd(t *testing.T) {
// Given a temporary directory with mocked dependencies
mocks := setupInitTest(t)

// And a composer with the mock blueprint handler
comp := composer.NewComposer(mocks.Runtime, &composer.Composer{
BlueprintHandler: mocks.BlueprintHandler,
})

// When executing the init command with blueprint flag
cmd := createTestInitCmd()
ctx := context.WithValue(context.Background(), runtimeOverridesKey, mocks.Runtime)
cmd.SetArgs([]string{"--blueprint", "full"})
ctx = context.WithValue(ctx, composerOverridesKey, comp)
cmd.SetArgs([]string{"--blueprint", "oci://ghcr.io/windsorcli/core:latest"})
cmd.SetContext(ctx)
err := cmd.Execute()

Expand Down Expand Up @@ -398,10 +406,16 @@ func TestInitCmd(t *testing.T) {
// Given a temporary directory with mocked dependencies
mocks := setupInitTest(t)

// And a composer with the mock blueprint handler
comp := composer.NewComposer(mocks.Runtime, &composer.Composer{
BlueprintHandler: mocks.BlueprintHandler,
})

// When executing the init command with multiple flags
cmd := createTestInitCmd()
ctx := context.WithValue(context.Background(), runtimeOverridesKey, mocks.Runtime)
cmd.SetArgs([]string{"--backend", "s3", "--vm-driver", "colima", "--docker", "--blueprint", "full"})
ctx = context.WithValue(ctx, composerOverridesKey, comp)
cmd.SetArgs([]string{"--backend", "s3", "--vm-driver", "colima", "--docker", "--blueprint", "oci://ghcr.io/windsorcli/core:latest"})
cmd.SetContext(ctx)
err := cmd.Execute()

Expand Down Expand Up @@ -791,9 +805,15 @@ func TestInitCmd(t *testing.T) {
// Given a temporary directory with mocked dependencies
mocks := setupInitTest(t)

// And a composer with the mock blueprint handler
comp := composer.NewComposer(mocks.Runtime, &composer.Composer{
BlueprintHandler: mocks.BlueprintHandler,
})

// When executing the init command with explicit blueprint
cmd := createTestInitCmd()
ctx := context.WithValue(context.Background(), runtimeOverridesKey, mocks.Runtime)
ctx = context.WithValue(ctx, composerOverridesKey, comp)
cmd.SetArgs([]string{"--blueprint", "oci://custom/blueprint:v1.0.0"})
cmd.SetContext(ctx)
err := cmd.Execute()
Expand Down
18 changes: 18 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ func setupMocks(t *testing.T, opts ...*SetupOptions) *Mocks {
return nil
}
}
if mockConfig.LoadSchemaFromBytesFunc == nil {
mockConfig.LoadSchemaFromBytesFunc = func(data []byte) error {
return nil
}
}
if mockConfig.LoadConfigStringFunc == nil {
mockConfig.LoadConfigStringFunc = func(content string) error {
// Parse YAML content if provided
Expand All @@ -215,6 +220,19 @@ func setupMocks(t *testing.T, opts ...*SetupOptions) *Mocks {
return ""
}
}
if mockConfig.GetContextValuesFunc == nil {
mockConfig.GetContextValuesFunc = func() (map[string]any, error) {
addons := make(map[string]any)
// Initialize common addons with enabled: false to prevent evaluation errors
for _, addon := range []string{"object_store", "observability", "private_ca", "private_dns"} {
addons[addon] = map[string]any{"enabled": false}
}
return map[string]any{
"addons": addons,
"dev": false,
}, nil
}
}
}

// Load config if ConfigStr is provided
Expand Down
2 changes: 1 addition & 1 deletion cmd/up_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func setupUpTest(t *testing.T, opts ...*SetupOptions) *UpMocks {

// Add blueprint handler mock
mockBlueprintHandler := blueprint.NewMockBlueprintHandler()
mockBlueprintHandler.LoadBlueprintFunc = func() error { return nil }
mockBlueprintHandler.LoadBlueprintFunc = func(...string) error { return nil }
mockBlueprintHandler.WriteFunc = func(overwrite ...bool) error { return nil }
testBlueprint := &blueprintv1alpha1.Blueprint{
Metadata: blueprintv1alpha1.Metadata{Name: "test"},
Expand Down
Loading
Loading