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
3 changes: 3 additions & 0 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
initPlatform string
initEndpoint string
initSetFlags []string
reset bool
)

var initCmd = &cobra.Command{
Expand Down Expand Up @@ -212,6 +213,7 @@ var initCmd = &cobra.Command{
Blueprint: true,
Generators: true,
Stack: true,
Reset: reset,
CommandName: cmd.Name(),
Flags: map[string]bool{
"verbose": verbose,
Expand Down Expand Up @@ -251,5 +253,6 @@ func init() {
initCmd.Flags().StringVar(&initBlueprint, "blueprint", "", "Specify the blueprint to use")
initCmd.Flags().StringVar(&initEndpoint, "endpoint", "", "Specify the kubernetes API endpoint")
initCmd.Flags().StringSliceVar(&initSetFlags, "set", []string{}, "Override configuration values. Example: --set dns.enabled=false --set cluster.endpoint=https://localhost:6443")
initCmd.Flags().BoolVar(&reset, "reset", false, "Reset/overwrite existing files and clean .terraform directory")
rootCmd.AddCommand(initCmd)
}
75 changes: 38 additions & 37 deletions pkg/blueprint/blueprint_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ import (

type BlueprintHandler interface {
Initialize() error
LoadConfig(path ...string) error
WriteConfig(path ...string) error
LoadConfig(reset ...bool) error
WriteConfig(overwrite ...bool) error
Install() error
GetMetadata() blueprintv1alpha1.Metadata
GetSources() []blueprintv1alpha1.Source
Expand Down Expand Up @@ -133,33 +133,37 @@ func (b *BaseBlueprintHandler) Initialize() error {
return nil
}

// LoadConfig reads and processes blueprint configuration from either a specified path or the default location.
// It supports both Jsonnet and YAML formats, evaluates any Jsonnet templates with the current context,
// and merges local blueprint data. The function handles default blueprints when no config exists.
func (b *BaseBlueprintHandler) LoadConfig(path ...string) error {
// LoadConfig reads blueprint configuration from specified path or default location.
// Priority: blueprint.yaml (if !reset), blueprint.jsonnet, platform template, default.
// Processes Jsonnet templates with context data injection for dynamic configuration.
// Falls back to embedded defaults if no configuration files exist.
func (b *BaseBlueprintHandler) LoadConfig(reset ...bool) error {
shouldReset := false
if len(reset) > 0 {
shouldReset = reset[0]
}

configRoot, err := b.configHandler.GetConfigRoot()
if err != nil {
return fmt.Errorf("error getting config root: %w", err)
}

basePath := filepath.Join(configRoot, "blueprint")
if len(path) > 0 && path[0] != "" {
basePath = path[0]
}

yamlPath := basePath + ".yaml"
jsonnetPath := basePath + ".jsonnet"

// 1. blueprint.yaml
if _, err := b.shims.Stat(yamlPath); err == nil {
yamlData, err := b.shims.ReadFile(yamlPath)
if err != nil {
return err
}
if err := b.processBlueprintData(yamlData, &b.blueprint); err != nil {
return err
if !shouldReset {
// 1. blueprint.yaml
if _, err := b.shims.Stat(yamlPath); err == nil {
yamlData, err := b.shims.ReadFile(yamlPath)
if err != nil {
return err
}
if err := b.processBlueprintData(yamlData, &b.blueprint); err != nil {
return err
}
return nil
}
return nil
}

// 2. blueprint.jsonnet
Expand Down Expand Up @@ -251,47 +255,44 @@ func (b *BaseBlueprintHandler) LoadConfig(path ...string) error {
// WriteConfig persists the current blueprint configuration to disk. It handles path resolution,
// directory creation, and writes the blueprint in YAML format. The function cleans sensitive or
// redundant data before writing, such as Terraform component variables/values and empty PostBuild configs.
func (b *BaseBlueprintHandler) WriteConfig(path ...string) error {
finalPath := ""
if len(path) > 0 && path[0] != "" {
finalPath = path[0]
} else {
configRoot, err := b.configHandler.GetConfigRoot()
if err != nil {
return fmt.Errorf("error getting config root: %w", err)
}
finalPath = filepath.Join(configRoot, "blueprint.yaml")
func (b *BaseBlueprintHandler) WriteConfig(overwrite ...bool) error {
shouldOverwrite := false
if len(overwrite) > 0 {
shouldOverwrite = overwrite[0]
}

configRoot, err := b.configHandler.GetConfigRoot()
if err != nil {
return fmt.Errorf("error getting config root: %w", err)
}

finalPath := filepath.Join(configRoot, "blueprint.yaml")
dir := filepath.Dir(finalPath)
if err := b.shims.MkdirAll(dir, os.ModePerm); err != nil {
if err := b.shims.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("error creating directory: %w", err)
}

if _, err := b.shims.Stat(finalPath); err == nil {
return nil
if !shouldOverwrite {
if _, err := b.shims.Stat(finalPath); err == nil {
return nil
}
}

fullBlueprint := b.blueprint.DeepCopy()

for i := range fullBlueprint.TerraformComponents {
fullBlueprint.TerraformComponents[i].Values = nil
}

for i := range fullBlueprint.Kustomizations {
postBuild := fullBlueprint.Kustomizations[i].PostBuild
if postBuild != nil && len(postBuild.Substitute) == 0 && len(postBuild.SubstituteFrom) == 0 {
fullBlueprint.Kustomizations[i].PostBuild = nil
}
}

fullBlueprint.Merge(&b.localBlueprint)

data, err := b.shims.YamlMarshalNonNull(fullBlueprint)
if err != nil {
return fmt.Errorf("error marshalling yaml: %w", err)
}

if err := b.shims.WriteFile(finalPath, data, 0644); err != nil {
return fmt.Errorf("error writing blueprint file: %w", err)
}
Expand Down
13 changes: 6 additions & 7 deletions pkg/blueprint/blueprint_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,9 +569,8 @@ func TestBlueprintHandler_LoadConfig(t *testing.T) {
return nil, os.ErrNotExist
}

// When loading config with a custom path
customPath := "/custom/path/blueprint"
err := handler.LoadConfig(customPath)
// When loading config
err := handler.LoadConfig()

// Then no error should be returned
if err != nil {
Expand All @@ -580,12 +579,12 @@ func TestBlueprintHandler_LoadConfig(t *testing.T) {

// And only yaml path should be checked since it exists
expectedPaths := []string{
customPath + ".yaml",
"blueprint.yaml",
}
for _, expected := range expectedPaths {
found := false
for _, checked := range checkedPaths {
if checked == expected {
if strings.HasSuffix(checked, expected) {
found = true
break
}
Expand Down Expand Up @@ -2092,8 +2091,8 @@ func TestBlueprintHandler_SetRepository(t *testing.T) {
}

func TestBaseBlueprintHandler_WaitForKustomizations(t *testing.T) {
const pollInterval = 50 * time.Millisecond
const kustomTimeout = 300 * time.Millisecond
const pollInterval = 45 * time.Millisecond
const kustomTimeout = 500 * time.Millisecond

t.Run("AllKustomizationsReady", func(t *testing.T) {
// Given a blueprint handler with multiple kustomizations that are all ready
Expand Down
12 changes: 6 additions & 6 deletions pkg/blueprint/mock_blueprint_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
// MockBlueprintHandler is a mock implementation of BlueprintHandler interface for testing
type MockBlueprintHandler struct {
InitializeFunc func() error
LoadConfigFunc func(path ...string) error
LoadConfigFunc func(reset ...bool) error
GetMetadataFunc func() blueprintv1alpha1.Metadata
GetSourcesFunc func() []blueprintv1alpha1.Source
GetTerraformComponentsFunc func() []blueprintv1alpha1.TerraformComponent
Expand All @@ -17,7 +17,7 @@ type MockBlueprintHandler struct {
SetSourcesFunc func(sources []blueprintv1alpha1.Source) error
SetTerraformComponentsFunc func(terraformComponents []blueprintv1alpha1.TerraformComponent) error
SetKustomizationsFunc func(kustomizations []blueprintv1alpha1.Kustomization) error
WriteConfigFunc func(path ...string) error
WriteConfigFunc func(overwrite ...bool) error
InstallFunc func() error
GetRepositoryFunc func() blueprintv1alpha1.Repository
SetRepositoryFunc func(repository blueprintv1alpha1.Repository) error
Expand Down Expand Up @@ -47,9 +47,9 @@ func (m *MockBlueprintHandler) Initialize() error {
}

// LoadConfig calls the mock LoadConfigFunc if set, otherwise returns nil
func (m *MockBlueprintHandler) LoadConfig(path ...string) error {
func (m *MockBlueprintHandler) LoadConfig(reset ...bool) error {
if m.LoadConfigFunc != nil {
return m.LoadConfigFunc(path...)
return m.LoadConfigFunc(reset...)
}
return nil
}
Expand Down Expand Up @@ -123,9 +123,9 @@ func (m *MockBlueprintHandler) SetKustomizations(kustomizations []blueprintv1alp
}

// WriteConfig calls the mock WriteConfigFunc if set, otherwise returns nil
func (m *MockBlueprintHandler) WriteConfig(path ...string) error {
func (m *MockBlueprintHandler) WriteConfig(overwrite ...bool) error {
if m.WriteConfigFunc != nil {
return m.WriteConfigFunc(path...)
return m.WriteConfigFunc(overwrite...)
}
return nil
}
Expand Down
16 changes: 8 additions & 8 deletions pkg/blueprint/mock_blueprint_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ func TestMockBlueprintHandler_LoadConfig(t *testing.T) {

mockLoadErr := fmt.Errorf("mock load config error")

t.Run("WithPath", func(t *testing.T) {
t.Run("WithReset", func(t *testing.T) {
// Given a mock handler with load config function
handler := setup(t)
handler.LoadConfigFunc = func(path ...string) error {
handler.LoadConfigFunc = func(reset ...bool) error {
return mockLoadErr
}
// When loading config with path
err := handler.LoadConfig("some/path")
// When loading config with reset
err := handler.LoadConfig(true)
// Then expected error should be returned
if err != mockLoadErr {
t.Errorf("Expected error = %v, got = %v", mockLoadErr, err)
Expand All @@ -75,7 +75,7 @@ func TestMockBlueprintHandler_LoadConfig(t *testing.T) {
// Given a mock handler without load config function
handler := setup(t)
// When loading config
err := handler.LoadConfig("some/path")
err := handler.LoadConfig()
// Then no error should be returned
if err != nil {
t.Errorf("Expected error = %v, got = %v", nil, err)
Expand Down Expand Up @@ -380,11 +380,11 @@ func TestMockBlueprintHandler_WriteConfig(t *testing.T) {
t.Run("WithFuncSet", func(t *testing.T) {
// Given a mock handler with write config function
handler := setup(t)
handler.WriteConfigFunc = func(path ...string) error {
handler.WriteConfigFunc = func(overwrite ...bool) error {
return mockWriteErr
}
// When writing config
err := handler.WriteConfig("some/path")
err := handler.WriteConfig()
// Then expected error should be returned
if err != mockWriteErr {
t.Errorf("Expected error = %v, got = %v", mockWriteErr, err)
Expand All @@ -395,7 +395,7 @@ func TestMockBlueprintHandler_WriteConfig(t *testing.T) {
// Given a mock handler without write config function
handler := setup(t)
// When writing config
err := handler.WriteConfig("some/path")
err := handler.WriteConfig()
// Then no error should be returned
if err != nil {
t.Errorf("Expected error = %v, got = %v", nil, err)
Expand Down
7 changes: 4 additions & 3 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type Requirements struct {
// Command info for context-specific decisions
CommandName string // Name of the command
Flags map[string]bool // Important flags that affect initialization
Reset bool // Whether to reset/overwrite existing files
}

// =============================================================================
Expand Down Expand Up @@ -384,7 +385,7 @@ func (c *BaseController) InitializeComponents() error {
if err := blueprintHandler.Initialize(); err != nil {
return fmt.Errorf("error initializing blueprint handler: %w", err)
}
if err := blueprintHandler.LoadConfig(); err != nil {
if err := blueprintHandler.LoadConfig(c.requirements.Reset); err != nil {
return fmt.Errorf("error loading blueprint config: %w", err)
}
}
Expand Down Expand Up @@ -439,7 +440,7 @@ func (c *BaseController) WriteConfigurationFiles() error {
if req.Blueprint {
blueprintHandler := c.ResolveBlueprintHandler()
if blueprintHandler != nil {
if err := blueprintHandler.WriteConfig(); err != nil {
if err := blueprintHandler.WriteConfig(req.Reset); err != nil {
return fmt.Errorf("error writing blueprint config: %w", err)
}
}
Expand Down Expand Up @@ -482,7 +483,7 @@ func (c *BaseController) WriteConfigurationFiles() error {
generators := c.ResolveAllGenerators()
for _, generator := range generators {
if generator != nil {
if err := generator.Write(); err != nil {
if err := generator.Write(req.Reset); err != nil {
return fmt.Errorf("error writing generator config: %w", err)
}
}
Expand Down
25 changes: 19 additions & 6 deletions pkg/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,7 @@ func TestBaseController_InitializeComponents(t *testing.T) {
mockBlueprint.InitializeFunc = func() error {
return nil
}
mockBlueprint.LoadConfigFunc = func(path ...string) error {
mockBlueprint.LoadConfigFunc = func(reset ...bool) error {
return fmt.Errorf("blueprint config loading failed")
}
mocks.Injector.Register("blueprintHandler", mockBlueprint)
Expand Down Expand Up @@ -955,7 +955,7 @@ func TestBaseController_InitializeComponents(t *testing.T) {
return nil
}

mockBlueprint.LoadConfigFunc = func(path ...string) error {
mockBlueprint.LoadConfigFunc = func(reset ...bool) error {
initialized["blueprintHandlerLoadConfig"] = true
return nil
}
Expand Down Expand Up @@ -1053,7 +1053,7 @@ func TestBaseController_WriteConfigurationFiles(t *testing.T) {

// Mock blueprint handler
mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector)
mockBlueprintHandler.WriteConfigFunc = func(path ...string) error {
mockBlueprintHandler.WriteConfigFunc = func(overwrite ...bool) error {
return nil
}
mocks.Injector.Register("blueprintHandler", mockBlueprintHandler)
Expand Down Expand Up @@ -1081,7 +1081,7 @@ func TestBaseController_WriteConfigurationFiles(t *testing.T) {

// Mock generators
mockGenerator := generators.NewMockGenerator()
mockGenerator.WriteFunc = func() error {
mockGenerator.WriteFunc = func(overwrite ...bool) error {
return nil
}
mocks.Injector.Register("generator", mockGenerator)
Expand Down Expand Up @@ -1166,7 +1166,7 @@ func TestBaseController_WriteConfigurationFiles(t *testing.T) {

// And a blueprint handler that fails to write config
mockBlueprintHandler := blueprint.NewMockBlueprintHandler(mocks.Injector)
mockBlueprintHandler.WriteConfigFunc = func(path ...string) error {
mockBlueprintHandler.WriteConfigFunc = func(overwrite ...bool) error {
return fmt.Errorf("blueprint config write failed")
}
mocks.Injector.Register("blueprintHandler", mockBlueprintHandler)
Expand Down Expand Up @@ -1290,7 +1290,7 @@ func TestBaseController_WriteConfigurationFiles(t *testing.T) {

// And a generator that fails to write config
mockGenerator := generators.NewMockGenerator()
mockGenerator.WriteFunc = func() error {
mockGenerator.WriteFunc = func(overwrite ...bool) error {
return fmt.Errorf("generator config write failed")
}
mocks.Injector.Register("generator", mockGenerator)
Expand Down Expand Up @@ -3877,3 +3877,16 @@ func TestBaseController_createVirtualizationComponents(t *testing.T) {
}
})
}

// Helper: BlueprintHandlerMock implements blueprint.BlueprintHandler
// (wraps the generated mock and adapts LoadConfig signature)
type BlueprintHandlerMock struct {
*blueprint.MockBlueprintHandler
}

func (m *BlueprintHandlerMock) LoadConfig(reset ...bool) error {
if m.LoadConfigFunc != nil {
return m.LoadConfigFunc(reset...)
}
return nil
}
Loading
Loading