From 763882642e36d9127d2fe293bc5fb0378cb044ef Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Tue, 25 Nov 2025 21:20:03 -0500 Subject: [PATCH] fix(blueprint): Fix input injection and cleanup Cleaned up redundant keys on `templateData` and cleared Inputs prior to processing Features. This fixes an issue that prevented writing inputs to tfvars files. Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- pkg/composer/blueprint/blueprint_handler.go | 48 ++++++++----------- .../blueprint_handler_private_test.go | 11 +---- .../blueprint_handler_public_test.go | 14 +++--- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/pkg/composer/blueprint/blueprint_handler.go b/pkg/composer/blueprint/blueprint_handler.go index d082c834b..aac79eabe 100644 --- a/pkg/composer/blueprint/blueprint_handler.go +++ b/pkg/composer/blueprint/blueprint_handler.go @@ -431,7 +431,6 @@ func (b *BaseBlueprintHandler) GetLocalTemplateData() (map[string][]byte, error) return nil, fmt.Errorf("failed to marshal composed blueprint: %w", err) } templateData["blueprint"] = composedBlueprintYAML - templateData["_template/blueprint.yaml"] = composedBlueprintYAML } var substitutionValues map[string]any @@ -567,6 +566,9 @@ func (b *BaseBlueprintHandler) processOCIArtifact(templateData map[string][]byte } } } + if blueprintData, exists := templateData["blueprint"]; exists { + featureTemplateData["blueprint"] = blueprintData + } b.featureEvaluator.SetTemplateData(templateData) @@ -758,7 +760,6 @@ func (b *BaseBlueprintHandler) getKustomizations() []blueprintv1alpha1.Kustomiza // walkAndCollectTemplates recursively traverses the specified template directory and collects all files into the // templateData map. It adds the contents of each file by a normalized relative path key prefixed with "_template/". -// Special files "schema.yaml" and "blueprint.yaml" are also stored under canonical keys ("schema", "blueprint"). // Directory entries are processed recursively. Any file or directory traversal errors are returned. func (b *BaseBlueprintHandler) walkAndCollectTemplates(templateDir string, templateData map[string][]byte) error { entries, err := b.shims.ReadDir(templateDir) @@ -787,36 +788,23 @@ func (b *BaseBlueprintHandler) walkAndCollectTemplates(templateDir string, templ relPath = strings.ReplaceAll(relPath, "\\", "/") key := "_template/" + relPath - switch entry.Name() { - case "schema.yaml": - templateData["schema"] = content - templateData[key] = content - case "blueprint.yaml": - templateData["blueprint"] = content - templateData[key] = content - default: - templateData[key] = content - } + templateData[key] = content } } return nil } -// processFeatures loads the base blueprint and merges features that match evaluated conditions. -// It loads the base blueprint.yaml from templateData, loads features, evaluates their When expressions -// against the provided config, and merges matching features into the base blueprint. Features and their -// components are merged in deterministic order by feature name. +// processFeatures applies blueprint features by evaluating conditional expressions and merging matching feature content into the blueprint. +// It loads the base blueprint from the template data (from canonical or alternate blueprint file keys), unmarshals and merges it. +// Then, it loads all features from template data, sorts them deterministically by feature name, and for each feature that matches +// its condition (`When`), merges its Terraform components and Kustomizations that also match their conditions. Component and kustomization +// inputs, substitutions, and patches are evaluated and processed per strategy, and the resulting objects are merged or replaced in the blueprint +// according to the specified merge strategy. func (b *BaseBlueprintHandler) processFeatures(templateData map[string][]byte, config map[string]any) error { - var blueprintData []byte - var exists bool - - if blueprintData, exists = templateData["blueprint"]; !exists { - if blueprintData, exists = templateData["_template/blueprint.yaml"]; !exists { - if blueprintData, exists = templateData["blueprint.yaml"]; !exists { - blueprintData = nil - } - } + blueprintData, _ := templateData["_template/blueprint.yaml"] + if blueprintData == nil { + blueprintData, _ = templateData["blueprint"] } if blueprintData != nil { @@ -903,6 +891,8 @@ func (b *BaseBlueprintHandler) processFeatures(templateData map[string][]byte, c component.Inputs = b.deepMergeMaps(component.Inputs, filteredInputs) } } + } else { + component.Inputs = nil } strategy := terraformComponent.Strategy @@ -960,7 +950,6 @@ func (b *BaseBlueprintHandler) processFeatures(templateData map[string][]byte, c } } - // Clear substitutions as they are used for ConfigMap generation and should not appear in the final blueprint kustomizationCopy.Substitutions = nil strategy := kustomization.Strategy @@ -1181,7 +1170,9 @@ func (b *BaseBlueprintHandler) processLocalArtifact(templateData map[string][]by } if _, exists := templateData["_template/blueprint.yaml"]; !exists { - return fmt.Errorf("blueprint.yaml not found in artifact template data") + if _, exists := templateData["blueprint"]; !exists { + return fmt.Errorf("blueprint not found in artifact template data") + } } if schemaData, exists := templateData["_template/schema.yaml"]; exists { @@ -1205,6 +1196,9 @@ func (b *BaseBlueprintHandler) processLocalArtifact(templateData map[string][]by } } } + if blueprintData, exists := templateData["blueprint"]; exists { + featureTemplateData["blueprint"] = blueprintData + } b.featureEvaluator.SetTemplateData(templateData) diff --git a/pkg/composer/blueprint/blueprint_handler_private_test.go b/pkg/composer/blueprint/blueprint_handler_private_test.go index ac91e4bec..bda632c7b 100644 --- a/pkg/composer/blueprint/blueprint_handler_private_test.go +++ b/pkg/composer/blueprint/blueprint_handler_private_test.go @@ -328,13 +328,6 @@ func TestBaseBlueprintHandler_walkAndCollectTemplates(t *testing.T) { t.Errorf("Expected no error, got: %v", err) } - if _, exists := templateData["schema"]; !exists { - t.Error("Expected 'schema' key to exist") - } - if _, exists := templateData["blueprint"]; !exists { - t.Error("Expected 'blueprint' key to exist") - } - if _, exists := templateData["_template/schema.yaml"]; !exists { t.Error("Expected '_template/schema.yaml' key to exist") } @@ -5542,8 +5535,8 @@ metadata: if err == nil { t.Fatal("Expected error when blueprint.yaml is missing") } - if !strings.Contains(err.Error(), "blueprint.yaml not found") { - t.Errorf("Expected error about missing blueprint.yaml, got: %v", err) + if !strings.Contains(err.Error(), "blueprint not found") { + t.Errorf("Expected error about missing blueprint, got: %v", err) } }) diff --git a/pkg/composer/blueprint/blueprint_handler_public_test.go b/pkg/composer/blueprint/blueprint_handler_public_test.go index b1ff5221c..f2cb7f704 100644 --- a/pkg/composer/blueprint/blueprint_handler_public_test.go +++ b/pkg/composer/blueprint/blueprint_handler_public_test.go @@ -2574,10 +2574,12 @@ metadata: // .jsonnet files are not collected in templateData; they are processed on-demand via jsonnet() function calls during feature evaluation - if content, exists := templateData["_template/blueprint.yaml"]; exists { + if content, exists := templateData["blueprint"]; exists { if !strings.Contains(string(content), contextName) { t.Errorf("Expected blueprint content to contain context name '%s', got: %s", contextName, string(content)) } + } else { + t.Error("Expected composed blueprint in templateData") } if content, exists := templateData["_template/features/aws.yaml"]; exists { @@ -2897,7 +2899,7 @@ terraform: t.Fatalf("Expected no error, got %v", err) } - composedBlueprint, exists := templateData["_template/blueprint.yaml"] + composedBlueprint, exists := templateData["blueprint"] if !exists { t.Fatal("Expected composed blueprint in templateData") } @@ -2967,7 +2969,7 @@ terraform: t.Fatalf("Expected no error, got %v", err) } - composedBlueprint, exists := templateData["_template/blueprint.yaml"] + composedBlueprint, exists := templateData["blueprint"] if !exists { t.Fatal("Expected composed blueprint in templateData") } @@ -3067,7 +3069,7 @@ terraform: t.Fatalf("Expected no error, got %v", err) } - composedBlueprint, exists := templateData["_template/blueprint.yaml"] + composedBlueprint, exists := templateData["blueprint"] if !exists { t.Fatal("Expected composed blueprint in templateData") } @@ -3144,7 +3146,7 @@ kustomize: t.Fatalf("Expected no error, got %v", err) } - composedBlueprint, exists := templateData["_template/blueprint.yaml"] + composedBlueprint, exists := templateData["blueprint"] if !exists { t.Fatal("Expected composed blueprint in templateData") } @@ -3206,7 +3208,7 @@ metadata: t.Fatalf("Expected no error, got %v", err) } - if composedBlueprint, exists := templateData["_template/blueprint.yaml"]; exists { + if composedBlueprint, exists := templateData["blueprint"]; exists { var blueprint blueprintv1alpha1.Blueprint if err := yaml.Unmarshal(composedBlueprint, &blueprint); err != nil { t.Fatalf("Failed to unmarshal blueprint: %v", err)