212 lines
8.4 KiB
Go
212 lines
8.4 KiB
Go
package engine
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRelativeImportsFeedFinalInheritanceAndInspectionLifecycle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type entry struct {
|
|
Extends string `yaml:"extends"`
|
|
Label string `yaml:"label"`
|
|
Token *Secret `yaml:"token" configkit:"secret"`
|
|
Dynamic string `yaml:"dynamic"`
|
|
Derived string `yaml:"derived"`
|
|
Height int `yaml:"height"`
|
|
}
|
|
type configuration struct {
|
|
Catalog struct {
|
|
Entries map[string]entry `yaml:"entries"`
|
|
} `yaml:"catalog"`
|
|
}
|
|
|
|
load := func(dynamic string) *testResult[configuration] {
|
|
t.Helper()
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "entry.yml", `configkit:
|
|
imports:
|
|
- path: parent.yml
|
|
destination: [entries]
|
|
- path: child.yml
|
|
destination: [entries]
|
|
`)
|
|
writeImportFile(t, directory, "parent.yml", `parent:
|
|
label: lower
|
|
token: PARENT_TOKEN
|
|
height: 1
|
|
`)
|
|
writeImportFile(t, directory, "child.yml", fmt.Sprintf(`child:
|
|
extends: parent
|
|
label: child
|
|
dynamic: %s
|
|
height: 9
|
|
`, dynamic))
|
|
|
|
var defaultsCalls, resolveCalls, validateCalls int
|
|
secretPaths := make(map[string]bool)
|
|
lookupCalls := make(map[string]int)
|
|
request := loadRequest[configuration]{
|
|
main: loadInput{
|
|
source: Bytes("main", []byte("parent: {label: final, height: 4}\n")),
|
|
destination: Keys("catalog", "entries"),
|
|
},
|
|
underlays: []loadGroup{{inputs: []loadInput{{
|
|
source: File(mainPath), imports: true, destination: Keys("catalog"),
|
|
}}}},
|
|
definition: Definition[configuration]{
|
|
Defaults: func() configuration {
|
|
defaultsCalls++
|
|
var defaults configuration
|
|
defaults.Catalog.Entries = map[string]entry{"fallback": {Label: "default"}}
|
|
return defaults
|
|
},
|
|
Resolve: func(config *configuration, resolution *Resolution) error {
|
|
resolveCalls++
|
|
child := config.Catalog.Entries["child"]
|
|
child.Derived = child.Label + ":" + child.Dynamic + ":" + fmt.Sprint(child.Height)
|
|
config.Catalog.Entries["child"] = child
|
|
resolution.Record(Keys("catalog", "entries", "child", "derived"), ChangeNormalization, "derive final child summary", Keys("catalog", "entries", "child", "label"), Keys("catalog", "entries", "child", "dynamic"), Keys("catalog", "entries", "child", "height"))
|
|
return nil
|
|
},
|
|
Validate: func(config *configuration, validation *Validation) {
|
|
validateCalls++
|
|
parent := config.Catalog.Entries["parent"]
|
|
child := config.Catalog.Entries["child"]
|
|
if parent.Label != "final" || parent.Height != 4 || child.Label != "child" || child.Height != 9 || child.Derived != child.Label+":"+child.Dynamic+":9" || child.Token == nil || !child.Token.IsResolved() || child.Token.Value() != "parent-secret-canary" {
|
|
validation.Error(Keys("catalog", "entries", "child"), "final imported configuration was not available to validation")
|
|
}
|
|
},
|
|
Secret: func(path Path) bool {
|
|
secretPaths[path.String()] = true
|
|
return path.String() == "/catalog/entries/child/dynamic"
|
|
},
|
|
},
|
|
}
|
|
loaded, err := loadGroupedWithSecretLookup(request, testSecretBinding(), func(name string) (string, bool) {
|
|
lookupCalls[name]++
|
|
if name == "PARENT_TOKEN" {
|
|
return "parent-secret-canary", true
|
|
}
|
|
return "", false
|
|
})
|
|
if err != nil {
|
|
if loadError, ok := err.(*LoadError); ok {
|
|
t.Fatalf("relative import lifecycle diagnostics: %#v", loadError.Diagnostics())
|
|
}
|
|
t.Fatalf("relative import lifecycle: %v", err)
|
|
}
|
|
if defaultsCalls != 1 || resolveCalls != 1 || validateCalls != 1 || lookupCalls["PARENT_TOKEN"] != 1 {
|
|
t.Fatalf("lifecycle calls defaults=%d resolve=%d validate=%d lookups=%#v", defaultsCalls, resolveCalls, validateCalls, lookupCalls)
|
|
}
|
|
if !secretPaths["/catalog/entries/child/dynamic"] {
|
|
t.Fatalf("secret classifier did not see effective paths: %#v", secretPaths)
|
|
}
|
|
return testLoaded(loaded)
|
|
}
|
|
|
|
left := load("LEFT-DYNAMIC-CANARY")
|
|
child := left.Config.Catalog.Entries["child"]
|
|
if child.Extends != "" || child.Label != "child" || child.Height != 9 || child.Derived != "child:LEFT-DYNAMIC-CANARY:9" || child.Token == nil || child.Token.Value() != "parent-secret-canary" {
|
|
t.Fatalf("resolved imported child = %#v", child)
|
|
}
|
|
if left.Config.Catalog.Entries["parent"].Label != "final" || left.Config.Catalog.Entries["parent"].Height != 4 || left.Config.Catalog.Entries["fallback"].Label != "default" {
|
|
t.Fatalf("final parent/default shape = %#v", left.Config.Catalog.Entries)
|
|
}
|
|
|
|
childTokenPath := Keys("catalog", "entries", "child", "token")
|
|
childToken, found := left.Inspection.Explain(childTokenPath)
|
|
childTokenInfo, tokenInfoFound := left.Inspection.Metadata().Lookup(childTokenPath)
|
|
if !found || !tokenInfoFound || !childToken.Effective.Redacted || strings.Contains(childToken.Effective.YAML, "parent-secret-canary") || len(childTokenInfo.Origin.Inheritance) != 1 {
|
|
t.Fatalf("inherited field explanation = %#v, %v", childToken, found)
|
|
}
|
|
var explanation bytes.Buffer
|
|
if err := left.Inspection.WriteExplanation(&explanation, childTokenPath); err != nil || strings.Contains(explanation.String(), "parent-secret-canary") || strings.Contains(explanation.String(), "LEFT-DYNAMIC-CANARY") {
|
|
t.Fatalf("safe explanation = %q, %v", explanation.String(), err)
|
|
}
|
|
labelHistory := left.Inspection.Metadata().History(Keys("catalog", "entries", "parent", "label"))
|
|
if len(labelHistory) < 2 {
|
|
t.Fatalf("parent label history omitted replaced contribution: %#v", labelHistory)
|
|
}
|
|
derived, found := left.Inspection.Explain(Keys("catalog", "entries", "child", "derived"))
|
|
if !found || !derived.Effective.Redacted || strings.Contains(derived.Effective.YAML, "LEFT-DYNAMIC-CANARY") || len(derived.History) == 0 || derived.History[len(derived.History)-1].Origin.Operation != ChangeNormalization {
|
|
t.Fatalf("recorded derived field explanation = %#v, %v", derived, found)
|
|
}
|
|
|
|
right := load("RIGHT-DYNAMIC-CANARY")
|
|
differences, err := Diff(left.Inspection, right.Inspection)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
seen := make(map[string]bool)
|
|
for _, difference := range differences {
|
|
path := difference.Path.String()
|
|
seen[path] = true
|
|
if !difference.Left.Redacted || !difference.Right.Redacted || !difference.HiddenChanged || strings.Contains(difference.Left.YAML+difference.Right.YAML, "DYNAMIC-CANARY") || strings.Contains(difference.Left.YAML+difference.Right.YAML, "parent-secret-canary") {
|
|
t.Errorf("sensitive difference at %s = %#v", path, difference)
|
|
}
|
|
}
|
|
for _, path := range []string{"/catalog/entries/child/dynamic", "/catalog/entries/child/derived"} {
|
|
if !seen[path] {
|
|
t.Errorf("missing inherited/derived difference at %s: %#v", path, differences)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRelativeImportMatchesWrappedPlacementAndSafeDiff(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
load := func(relativeImport bool, value string) *testResult[map[string]any] {
|
|
t.Helper()
|
|
input := Input{Source: NoSource()}
|
|
options := LayeredOptions[map[string]any]{}
|
|
if relativeImport {
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
|
imports:
|
|
- path: fragment.yml
|
|
destination: [application]
|
|
`)
|
|
writeImportFile(t, directory, "fragment.yml", "field: "+value+"\n")
|
|
options.Underlays = []Underlay{{Inputs: []Input{{Source: File(mainPath), Imports: true}}}}
|
|
} else {
|
|
options.Underlays = []Underlay{{Inputs: []Input{{Source: Bytes("wrapped", []byte("application: {field: "+value+"}\n"))}}}}
|
|
}
|
|
loaded, err := LoadLayered(input, testSecretBinding(), options)
|
|
if err != nil {
|
|
t.Fatalf("LoadLayered: %v", err)
|
|
}
|
|
return testLoaded(loaded)
|
|
}
|
|
|
|
relativeBefore := load(true, "before")
|
|
relativeAfter := load(true, "after")
|
|
wrappedBefore := load(false, "before")
|
|
wrappedAfter := load(false, "after")
|
|
for _, pair := range [][2]*testResult[map[string]any]{{relativeBefore, wrappedBefore}, {relativeAfter, wrappedAfter}} {
|
|
if !reflect.DeepEqual(pair[0].Config, pair[1].Config) {
|
|
t.Fatalf("relative and wrapped configurations differ: %#v vs %#v", pair[0].Config, pair[1].Config)
|
|
}
|
|
for _, result := range pair {
|
|
if info, ok := result.Metadata().Lookup(Keys("application", "field")); !ok || !info.Explicit {
|
|
t.Fatalf("effective field presence = %#v, %v", info, ok)
|
|
}
|
|
}
|
|
}
|
|
relativeDiff, err := Diff(relativeBefore.Inspection, relativeAfter.Inspection)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wrappedDiff, err := Diff(wrappedBefore.Inspection, wrappedAfter.Inspection)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(relativeDiff, wrappedDiff) {
|
|
t.Fatalf("relative safe diff = %#v, wrapped safe diff = %#v", relativeDiff, wrappedDiff)
|
|
}
|
|
}
|