From ba27050e96f3c286ffb52186a81cf4c6edb4821d Mon Sep 17 00:00:00 2001 From: Ryan VanGundy Date: Tue, 15 Jul 2025 09:02:44 -0400 Subject: [PATCH] fix(pipeline): Prefer existing blueprint.yaml if reset isn't set --- pkg/blueprint/blueprint_handler.go | 9 +-- pkg/blueprint/blueprint_handler_test.go | 33 ++++------ pkg/pipelines/init.go | 82 +++++++++++++++---------- 3 files changed, 63 insertions(+), 61 deletions(-) diff --git a/pkg/blueprint/blueprint_handler.go b/pkg/blueprint/blueprint_handler.go index cb3acbf24..26b7f9d6e 100644 --- a/pkg/blueprint/blueprint_handler.go +++ b/pkg/blueprint/blueprint_handler.go @@ -122,7 +122,7 @@ func (b *BaseBlueprintHandler) Initialize() error { } // LoadConfig reads blueprint configuration from blueprint.yaml file. -// Only loads existing blueprint.yaml files - no templating or generation. +// Returns an error if blueprint.yaml does not exist. // Template processing is now handled by the pkg/template package. func (b *BaseBlueprintHandler) LoadConfig() error { configRoot, err := b.configHandler.GetConfigRoot() @@ -132,12 +132,7 @@ func (b *BaseBlueprintHandler) LoadConfig() error { yamlPath := filepath.Join(configRoot, "blueprint.yaml") if _, err := b.shims.Stat(yamlPath); err != nil { - // No blueprint.yaml exists - use default blueprint - context := b.configHandler.GetContext() - b.blueprint = *DefaultBlueprint.DeepCopy() - b.blueprint.Metadata.Name = context - b.blueprint.Metadata.Description = fmt.Sprintf("This blueprint outlines resources in the %s context", context) - return nil + return fmt.Errorf("blueprint.yaml not found at %s", yamlPath) } yamlData, err := b.shims.ReadFile(yamlPath) diff --git a/pkg/blueprint/blueprint_handler_test.go b/pkg/blueprint/blueprint_handler_test.go index 31df7ad24..25454d6a8 100644 --- a/pkg/blueprint/blueprint_handler_test.go +++ b/pkg/blueprint/blueprint_handler_test.go @@ -640,18 +640,14 @@ func TestBlueprintHandler_LoadConfig(t *testing.T) { // When loading the config err := handler.LoadConfig() - // Then no error should be returned - if err != nil { - t.Errorf("Expected no error, got %v", err) + // Then an error should be returned since blueprint.yaml doesn't exist + if err == nil { + t.Errorf("Expected error when blueprint.yaml doesn't exist, got nil") } - // And the default metadata should be set correctly - metadata := handler.GetMetadata() - if metadata.Name != "local" { - t.Errorf("Expected name to be 'local', got %s", metadata.Name) - } - if metadata.Description != "This blueprint outlines resources in the local context" { - t.Errorf("Expected description to be 'This blueprint outlines resources in the local context', got %s", metadata.Description) + // And the error should indicate blueprint.yaml not found + if !strings.Contains(err.Error(), "blueprint.yaml not found") { + t.Errorf("Expected error about blueprint.yaml not found, got: %v", err) } }) @@ -812,19 +808,14 @@ func TestBlueprintHandler_LoadConfig(t *testing.T) { // When loading the config err := handler.LoadConfig() - // Then no error should be returned - if err != nil { - t.Errorf("Expected no error for empty evaluated jsonnet, got: %v", err) + // Then an error should be returned since blueprint.yaml doesn't exist + if err == nil { + t.Errorf("Expected error when blueprint.yaml doesn't exist, got nil") } - // And the default metadata should be set correctly - metadata := handler.GetMetadata() - if metadata.Name != "local" { - t.Errorf("Expected blueprint name to be 'local', got: %s", metadata.Name) - } - expectedDesc := "This blueprint outlines resources in the local context" - if metadata.Description != expectedDesc { - t.Errorf("Expected description '%s', got: %s", expectedDesc, metadata.Description) + // And the error should indicate blueprint.yaml not found + if !strings.Contains(err.Error(), "blueprint.yaml not found") { + t.Errorf("Expected error about blueprint.yaml not found, got: %v", err) } }) diff --git a/pkg/pipelines/init.go b/pkg/pipelines/init.go index 01b3debc4..bb1e47f30 100644 --- a/pkg/pipelines/init.go +++ b/pkg/pipelines/init.go @@ -213,84 +213,68 @@ func (p *InitPipeline) Initialize(injector di.Injector, ctx context.Context) err } } + if p.networkManager != nil { + if err := p.networkManager.Initialize(); err != nil { + return fmt.Errorf("failed to initialize network manager: %w", err) + } + } + return nil } -// Execute runs the init pipeline, performing trusted file setup, reset token writing, context ID generation, -// configuration saving, network manager initialization, template data preparation, template processing and -// blueprint generation, blueprint loading, terraform module resolution, and final file generation. -// All component initialization is performed in Initialize(). Phases are executed in strict order. +// Execute performs initialization by setting up trusted files, writing reset tokens, generating context IDs, +// configuring defaults, saving configuration, processing templates, handling blueprints separately, +// writing blueprint files, resolving Terraform modules, and generating final output files. func (p *InitPipeline) Execute(ctx context.Context) error { + + // Phase 1: Setup & configuration if err := p.shell.AddCurrentDirToTrustedFile(); err != nil { return fmt.Errorf("Error adding current directory to trusted file: %w", err) } - if _, err := p.shell.WriteResetToken(); err != nil { return fmt.Errorf("Error writing reset token: %w", err) } - if err := p.configHandler.GenerateContextID(); err != nil { return fmt.Errorf("failed to generate context ID: %w", err) } - reset := false if resetValue := ctx.Value("reset"); resetValue != nil { reset = resetValue.(bool) } - - // Set default configuration before saving contextName := p.determineContextName(ctx) if err := p.setDefaultConfiguration(ctx, contextName); err != nil { return err } - if err := p.saveConfiguration(reset); err != nil { return err } - // Network manager phase - if p.networkManager != nil { - if err := p.networkManager.Initialize(); err != nil { - return fmt.Errorf("failed to initialize network manager: %w", err) - } - } - - // Phase 1: template data preparation + // Phase 2: Template processing templateData, err := p.prepareTemplateData(ctx) if err != nil { return fmt.Errorf("failed to prepare template data: %w", err) } - - // Phase 2: template processing and blueprint generation renderedData, err := p.processTemplateData(templateData) if err != nil { return err } - if err := p.loadBlueprintFromTemplate(ctx, renderedData); err != nil { + // Phase 3: Blueprint handling + if err := p.handleBlueprintLoading(ctx, renderedData, reset); err != nil { return err } - - // Phase 3: blueprint loading (fallback if no template data) - if len(renderedData) == 0 || renderedData["blueprint"] == nil { - if err := p.blueprintHandler.LoadConfig(); err != nil { - return fmt.Errorf("Error loading blueprint config: %w", err) - } - } - - // Write blueprint file if err := p.blueprintHandler.Write(reset); err != nil { return fmt.Errorf("failed to write blueprint file: %w", err) } - // Phase 4: terraform module resolution + // Phase 4: Terraform module resolution for _, resolver := range p.terraformResolvers { if err := resolver.ProcessModules(); err != nil { return fmt.Errorf("failed to process terraform modules: %w", err) } } - // Phase 5: final generation + // Phase 5: Final file generation if len(renderedData) > 0 { for _, generator := range p.generators { if err := generator.Generate(renderedData, reset); err != nil { @@ -303,7 +287,6 @@ func (p *InitPipeline) Execute(ctx context.Context) error { return err } - // Print success message fmt.Fprintln(os.Stderr, "Initialization successful") return nil @@ -550,6 +533,39 @@ func (p *InitPipeline) writeConfigurationFiles() error { return nil } +// handleBlueprintLoading manages blueprint loading logic based on reset flag and existing blueprint files. +// If reset is true, loads blueprint from template data if available. +// If reset is false, prefers existing blueprint.yaml over template data. +// Falls back to loading from existing config if no template blueprint data exists. +func (p *InitPipeline) handleBlueprintLoading(ctx context.Context, renderedData map[string]any, reset bool) error { + shouldLoadFromTemplate := false + + if reset { + shouldLoadFromTemplate = true + } else { + configRoot, err := p.configHandler.GetConfigRoot() + if err != nil { + return fmt.Errorf("error getting config root: %w", err) + } + blueprintPath := filepath.Join(configRoot, "blueprint.yaml") + if _, err := p.shims.Stat(blueprintPath); err != nil { + shouldLoadFromTemplate = true + } + } + + if shouldLoadFromTemplate && len(renderedData) > 0 && renderedData["blueprint"] != nil { + if err := p.loadBlueprintFromTemplate(ctx, renderedData); err != nil { + return err + } + } else { + if err := p.blueprintHandler.LoadConfig(); err != nil { + return fmt.Errorf("Error loading blueprint config: %w", err) + } + } + + return nil +} + // ============================================================================= // Interface Compliance // =============================================================================