327 lines
12 KiB
Go
327 lines
12 KiB
Go
package engine
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"testing/fstest"
|
|
)
|
|
|
|
type importDestinationConfig struct {
|
|
Mounted map[string]string `yaml:"mounted"`
|
|
Collections map[string]map[string]string `yaml:"collections"`
|
|
}
|
|
|
|
func TestLayeredHostImportsInheritInputDestinationTransitively(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
|
imports:
|
|
- path: first.yml
|
|
- path: second.yml
|
|
root: main
|
|
`)
|
|
firstPath := writeImportFile(t, directory, "first.yml", `configkit:
|
|
imports:
|
|
- path: nested/third.yml
|
|
first: one
|
|
`)
|
|
thirdPath := writeImportFile(t, directory, "nested/third.yml", "nested: three\n")
|
|
secondPath := writeImportFile(t, directory, "second.yml", "second: two\n")
|
|
|
|
loaded, err := LoadLayered(
|
|
Input{Source: File(mainPath), Imports: true, Destination: Keys("mounted")},
|
|
testSecretBinding(),
|
|
LayeredOptions[importDestinationConfig]{},
|
|
)
|
|
if err != nil {
|
|
if loadError, ok := err.(*LoadError); ok {
|
|
t.Fatalf("LoadLayered diagnostics: %#v", loadError.Diagnostics())
|
|
}
|
|
t.Fatalf("LoadLayered: %v", err)
|
|
}
|
|
result := testLoaded(loaded)
|
|
want := map[string]string{"root": "main", "first": "one", "nested": "three", "second": "two"}
|
|
if len(result.Config.Mounted) != len(want) {
|
|
t.Fatalf("mounted values = %#v", result.Config.Mounted)
|
|
}
|
|
for key, value := range want {
|
|
if result.Config.Mounted[key] != value {
|
|
t.Errorf("mounted[%q] = %q, want %q", key, result.Config.Mounted[key], value)
|
|
}
|
|
}
|
|
if _, ok := result.Metadata().Lookup(Keys("mounted", "configkit")); ok {
|
|
t.Fatal("source-root import directive leaked into application content")
|
|
}
|
|
sources := result.Sources()
|
|
wantPaths := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, firstPath), canonicalTestPath(t, thirdPath), canonicalTestPath(t, secondPath)}
|
|
if len(sources) != len(wantPaths) {
|
|
t.Fatalf("sources = %#v", sources)
|
|
}
|
|
for index, source := range sources {
|
|
if source.Destination.String() != "/mounted" {
|
|
t.Errorf("source %d destination = %q", index, source.Destination.String())
|
|
}
|
|
if source.Path != wantPaths[index] {
|
|
t.Errorf("source %d path = %q, want %q", index, source.Path, wantPaths[index])
|
|
}
|
|
if index > 0 && source.ImportedFrom == nil {
|
|
t.Errorf("imported source %d has no physical import location", index)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestImportTraversalUsesPerDeclarationDestinations(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
|
imports:
|
|
- path: parent.yml
|
|
- path: sibling.yml
|
|
- glob: fragments/*.yml
|
|
`)
|
|
parentPath := writeImportFile(t, directory, "parent.yml", `configkit:
|
|
imports:
|
|
- path: nested/child.yml
|
|
parent: true
|
|
`)
|
|
childPath := writeImportFile(t, directory, "nested/child.yml", "child: true\n")
|
|
siblingPath := writeImportFile(t, directory, "sibling.yml", "sibling: true\n")
|
|
alphaPath := writeImportFile(t, directory, "fragments/a.yml", "alpha: true\n")
|
|
betaPath := writeImportFile(t, directory, "fragments/b.yml", "beta: true\n")
|
|
|
|
main := parseImportMain(t, mainPath)
|
|
_, declarations, diagnostics := splitImportDirectives(main.document)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("main directive diagnostics = %#v", diagnostics)
|
|
}
|
|
declarations[0].destination = Keys("parent")
|
|
declarations[1].destination = Keys("sibling")
|
|
declarations[2].destination = Keys("collection")
|
|
|
|
access, err := openHostAccess(directory)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = access.close() }()
|
|
state := importTraversal{
|
|
access: access, accounting: &importAccounting{}, limits: defaultImportLimits(),
|
|
active: make(map[resourceIdentity]int), visited: make(map[resourceIdentity]importRoute),
|
|
}
|
|
if diagnostics := state.walkDeclarations(directory, declarations, 0, Keys("entry")); len(diagnostics) != 0 {
|
|
t.Fatalf("walk diagnostics = %#v", diagnostics)
|
|
}
|
|
|
|
wantSources := []struct {
|
|
path string
|
|
destination string
|
|
}{
|
|
{path: parentPath, destination: "/entry/parent"},
|
|
{path: childPath, destination: "/entry/parent"},
|
|
{path: siblingPath, destination: "/entry/sibling"},
|
|
{path: alphaPath, destination: "/entry/collection"},
|
|
{path: betaPath, destination: "/entry/collection"},
|
|
}
|
|
if len(state.sources) != len(wantSources) {
|
|
t.Fatalf("import sources = %#v, want %d", state.sources, len(wantSources))
|
|
}
|
|
for index, want := range wantSources {
|
|
got := state.sources[index]
|
|
if got.Path != canonicalTestPath(t, want.path) || got.Destination.String() != want.destination {
|
|
t.Errorf("source %d = (%q, %q), want (%q, %q)", index, got.Path, got.Destination.String(), canonicalTestPath(t, want.path), want.destination)
|
|
}
|
|
}
|
|
|
|
composed, diagnostics := composeDocuments(state.documents...)
|
|
if len(diagnostics) != 0 {
|
|
t.Fatalf("composition diagnostics = %#v", diagnostics)
|
|
}
|
|
for _, path := range []Path{
|
|
Keys("entry", "parent", "parent"), Keys("entry", "parent", "child"),
|
|
Keys("entry", "sibling", "sibling"), Keys("entry", "collection", "alpha"),
|
|
Keys("entry", "collection", "beta"),
|
|
} {
|
|
if _, present := documentAtPath(composed.root, path); !present {
|
|
t.Errorf("no composed value at %q", path.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEntryDestinationSurvivesImportSetupAndParseFailures(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("import context failure", func(t *testing.T) {
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: child.yml}]}\n")
|
|
input, diagnostics := assembleBaseInputTracked(
|
|
File(mainPath),
|
|
Options[map[string]any]{Imports: true, Filesystem: &FilesystemContext{Root: t.TempDir()}},
|
|
Keys("mounted"), &importAccounting{}, nil, nil,
|
|
)
|
|
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportOutsideRoot {
|
|
t.Fatalf("context diagnostics = %#v", diagnostics)
|
|
}
|
|
if len(input.sources) != 1 || input.sources[0].Destination.String() != "/mounted" {
|
|
t.Fatalf("entry source fallback = %#v", input.sources)
|
|
}
|
|
})
|
|
|
|
t.Run("imported parse failure", func(t *testing.T) {
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: broken.yml}]}\n")
|
|
writeImportFile(t, directory, "broken.yml", "broken: [\n")
|
|
input, diagnostics := assembleBaseInputTracked(
|
|
File(mainPath), Options[map[string]any]{Imports: true},
|
|
Keys("mounted"), &importAccounting{}, nil, nil,
|
|
)
|
|
if len(diagnostics) != 1 || diagnostics[0].Code != CodeYAMLInvalid {
|
|
t.Fatalf("parse diagnostics = %#v", diagnostics)
|
|
}
|
|
if len(input.sources) != 2 {
|
|
t.Fatalf("parse-failure sources = %#v", input.sources)
|
|
}
|
|
for index, source := range input.sources {
|
|
if source.Destination.String() != "/mounted" {
|
|
t.Errorf("source %d destination = %q, want /mounted", index, source.Destination.String())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestLayeredNativeFilesystemImportsInheritInputDestination(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
native := fstest.MapFS{
|
|
"main.yml": &fstest.MapFile{Data: []byte("configkit:\n imports:\n - path: first.yml\nroot: main\n")},
|
|
"first.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: nested.yml}]}\nfirst: imported\n")},
|
|
"nested.yml": &fstest.MapFile{Data: []byte("nested: imported\n")},
|
|
}
|
|
namespace := newNativeNamespace("bundle", native)
|
|
loaded, err := LoadLayered(
|
|
Input{Source: filesystemFile(namespace, "main.yml"), Imports: true, Destination: Keys("mounted")},
|
|
testSecretBinding(),
|
|
LayeredOptions[importDestinationConfig]{},
|
|
)
|
|
if err != nil {
|
|
if loadError, ok := err.(*LoadError); ok {
|
|
t.Fatalf("LoadLayered diagnostics: %#v", loadError.Diagnostics())
|
|
}
|
|
t.Fatalf("LoadLayered: %v", err)
|
|
}
|
|
result := testLoaded(loaded)
|
|
if result.Config.Mounted["root"] != "main" || result.Config.Mounted["first"] != "imported" || result.Config.Mounted["nested"] != "imported" {
|
|
t.Fatalf("mounted native filesystem values = %#v", result.Config.Mounted)
|
|
}
|
|
sources := result.Sources()
|
|
if len(sources) != 3 || sources[0].Destination.String() != "/mounted" || sources[1].Destination.String() != "/mounted" || sources[2].Destination.String() != "/mounted" {
|
|
t.Fatalf("native source destinations = %#v", sources)
|
|
}
|
|
}
|
|
|
|
func TestLayeredImportOnlyInputMaterializesDestination(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
directory := t.TempDir()
|
|
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
|
imports:
|
|
- path: optional.yml
|
|
optional: true
|
|
`)
|
|
loaded, err := LoadLayered(
|
|
Input{Source: File(mainPath), Imports: true, Destination: Keys("collections", "empty")},
|
|
testSecretBinding(),
|
|
LayeredOptions[importDestinationConfig]{},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("LoadLayered: %v", err)
|
|
}
|
|
result := testLoaded(loaded)
|
|
info, ok := result.Metadata().Lookup(Keys("collections", "empty"))
|
|
if !ok || !info.Explicit {
|
|
t.Fatalf("import-only body destination = %#v, %v", info, ok)
|
|
}
|
|
sources := result.Sources()
|
|
if len(sources) != 1 || sources[0].Destination.String() != "/collections/empty" {
|
|
t.Fatalf("sources = %#v", sources)
|
|
}
|
|
}
|
|
|
|
func TestLayeredImportedVersionClaimsUseEffectiveDestination(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
rulePath Path
|
|
destination Path
|
|
wantCode Code
|
|
}{
|
|
{name: "protected effective path", rulePath: Keys("mounted", "version"), destination: Keys("mounted"), wantCode: CodeImportVersion},
|
|
{name: "ordinary nested domain version", rulePath: Keys("version"), destination: Keys("collections", "entry")},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
directory := t.TempDir()
|
|
writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: fragment.yml}]}\n")
|
|
writeImportFile(t, directory, "fragment.yml", "version: v2\n")
|
|
rule := &VersionRule{Path: test.rulePath, Check: func(Version, *Validation) error { return nil }}
|
|
loaded, err := LoadLayered(
|
|
Input{Source: NoSource()},
|
|
testSecretBinding(),
|
|
LayeredOptions[importDestinationConfig]{
|
|
Definition: Definition[importDestinationConfig]{Version: rule},
|
|
Underlays: []Underlay{{Inputs: []Input{{
|
|
Source: File(filepath.Join(directory, "main.yml")), Imports: true, Destination: test.destination,
|
|
}}}},
|
|
},
|
|
)
|
|
if test.wantCode != "" {
|
|
if err == nil {
|
|
t.Fatal("protected imported version claim was accepted")
|
|
}
|
|
loadError, ok := err.(*LoadError)
|
|
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != test.wantCode {
|
|
t.Fatalf("load error = %#v", err)
|
|
}
|
|
if test.name == "protected effective path" {
|
|
sources := loadError.Sources()
|
|
if len(sources) != 2 || sources[0].Destination.String() != "/mounted" || sources[1].Destination.String() != "/mounted" {
|
|
t.Fatalf("error source destinations = %#v", sources)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("ordinary nested version was rejected: %v", err)
|
|
}
|
|
result := testLoaded(loaded)
|
|
if got := result.Config.Collections["entry"]["version"]; got != "v2" {
|
|
t.Fatalf("nested version = %q", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLayeredDestinationsDoNotChangeRepeatedSourceIdentity(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
directory := t.TempDir()
|
|
writeImportFile(t, directory, "shared.yml", "value: shared\n")
|
|
importerPath := writeImportFile(t, directory, "importer.yml", "configkit: {imports: [{path: shared.yml}]}\n")
|
|
_, err := LoadLayered(
|
|
Input{Source: NoSource()},
|
|
testSecretBinding(),
|
|
LayeredOptions[importDestinationConfig]{Underlays: []Underlay{{Inputs: []Input{
|
|
{Source: File(importerPath), Imports: true, Destination: Keys("imported")},
|
|
{Source: File(filepath.Join(directory, "shared.yml")), Destination: Keys("direct")},
|
|
}}}},
|
|
)
|
|
if err == nil {
|
|
t.Fatal("the same physical source was accepted at two destinations in one group")
|
|
}
|
|
loadError, ok := err.(*LoadError)
|
|
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != CodeImportRepeated {
|
|
t.Fatalf("load error = %#v", err)
|
|
}
|
|
}
|