Load ordinary overlays without a main source
This commit is contained in:
@@ -23,7 +23,8 @@ type Definition[T any] struct {
|
||||
// Options contains the application definition and per-load input selections.
|
||||
// Imports enables traversal of declarations in a selected main document; an
|
||||
// absent source has no main declarations to traverse. Overlay selects one fixed
|
||||
// overlay. Overrides contain only values explicitly supplied by an application CLI.
|
||||
// overlay and requires filesystem context for NoSource. Overrides contain only
|
||||
// values explicitly supplied by an application CLI.
|
||||
type Options[T any] struct {
|
||||
Definition Definition[T]
|
||||
Imports bool
|
||||
|
||||
@@ -31,8 +31,8 @@ func selectOptions[T any](source Source, supplied []Options[T]) (Options[T], []D
|
||||
if options.Filesystem != nil {
|
||||
diagnostics = append(diagnostics, filesystemOptionDiagnostics(source, options.Filesystem)...)
|
||||
}
|
||||
if source.kind == sourceNone && options.Overlay != "" && options.Overlay != "default" {
|
||||
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "NoSource cannot select an active overlay"))
|
||||
if source.kind == sourceNone && activeOverlay(options.Overlay) && options.Filesystem == nil {
|
||||
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "NoSource overlays require explicit filesystem context"))
|
||||
}
|
||||
if options.Filesystem == nil && (source.kind == sourceBytes || source.kind == sourceReader || source.kind == sourceMapping) && (options.Imports || activeOverlay(options.Overlay)) {
|
||||
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "synthetic-source imports or overlays require filesystem context"))
|
||||
|
||||
@@ -59,9 +59,24 @@ func loadSelectedOverlayIntoWithVersion(source Source, context *FilesystemContex
|
||||
}
|
||||
return loadSelectedOverlayWithAccessAndVersion(access, base, sources, name, rule, limits, versionState, policy)
|
||||
}
|
||||
rootPath, _, _, diagnostic := importFilesystem(source, context, main)
|
||||
if diagnostic != nil {
|
||||
return cloneSourceInfos(sources), []Diagnostic{*diagnostic}
|
||||
var rootPath string
|
||||
if source.kind == sourceNone {
|
||||
if context == nil {
|
||||
value := optionDiagnostic(CodeOptionsInvalid, "NoSource overlays require explicit filesystem context")
|
||||
return cloneSourceInfos(sources), []Diagnostic{value}
|
||||
}
|
||||
var err error
|
||||
rootPath, err = canonicalExistingPath(context.Root)
|
||||
if err != nil {
|
||||
value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil)
|
||||
return cloneSourceInfos(sources), []Diagnostic{value}
|
||||
}
|
||||
} else {
|
||||
var diagnostic *Diagnostic
|
||||
rootPath, _, _, diagnostic = importFilesystem(source, context, main)
|
||||
if diagnostic != nil {
|
||||
return cloneSourceInfos(sources), []Diagnostic{*diagnostic}
|
||||
}
|
||||
}
|
||||
access, err := openHostAccess(rootPath)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,11 +2,14 @@ package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
type overlayTestConfig struct {
|
||||
@@ -21,6 +24,81 @@ type overlayTestConfig struct {
|
||||
Schema map[string]string `yaml:"schema"`
|
||||
}
|
||||
|
||||
func TestNoSourceSelectedOverlayUsesRootedHostContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
type config struct {
|
||||
Resource string `yaml:"resource"`
|
||||
}
|
||||
|
||||
root := t.TempDir()
|
||||
overlayPath := writeImportFile(t, root, "overlays/testing.yml", "resource: asset.txt\n")
|
||||
writeImportFile(t, root, "overlays/asset.txt", "host asset\n")
|
||||
loaded, err := testLoad[config](NoSource(), Options[config]{
|
||||
Overlay: "testing", Filesystem: &FilesystemContext{Root: root},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sources := loaded.Sources()
|
||||
if len(sources) != 1 || sources[0].ID != "source-1" || sources[0].Path != canonicalTestPath(t, overlayPath) || sources[0].Layer != LayerOverlay || sources[0].Kind != SourceKindFile {
|
||||
t.Fatalf("NoSource overlay sources = %#v", sources)
|
||||
}
|
||||
context, ok := loaded.Inspection.SourceContext("source-1")
|
||||
if !ok || context.BaseDir != "overlays" {
|
||||
t.Fatalf("overlay resource context = %#v, %v", context, ok)
|
||||
}
|
||||
data, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, loaded.Config.Resource))
|
||||
if err != nil || string(data) != "host asset\n" {
|
||||
t.Fatalf("overlay resource = %q, %v", data, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSourceSelectedOverlayRequiresContextAndSelection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
requireLoadCode(t, NoSource(), Options[importTestConfig]{Overlay: "testing"}, CodeOptionsInvalid)
|
||||
|
||||
root := t.TempDir()
|
||||
requireLoadCode(t, NoSource(), Options[importTestConfig]{
|
||||
Overlay: "testing", Filesystem: &FilesystemContext{Root: root},
|
||||
}, CodeOverlayMissing)
|
||||
|
||||
outside := t.TempDir()
|
||||
requireLoadCode(t, NoSource(), Options[importTestConfig]{
|
||||
Overlay: "testing", Filesystem: &FilesystemContext{Root: root, BaseDir: outside},
|
||||
}, CodeImportOutsideRoot)
|
||||
}
|
||||
|
||||
func TestNoSourceSelectedOverlayUsesCompleteNativeContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
type config struct {
|
||||
Resource string `yaml:"resource"`
|
||||
}
|
||||
|
||||
filesystem := NewFilesystem("embedded", fstest.MapFS{
|
||||
"overlays/testing.yml": &fstest.MapFile{Data: []byte("resource: asset.txt\n")},
|
||||
"overlays/asset.txt": &fstest.MapFile{Data: []byte("native asset\n")},
|
||||
})
|
||||
loaded, err := testLoad[config](NoSource(), Options[config]{
|
||||
Overlay: "testing", Filesystem: &FilesystemContext{FS: &filesystem, BaseDir: ".", Root: "."},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sources := loaded.Sources()
|
||||
if len(sources) != 1 || sources[0].ID != "source-1" || sources[0].Path != "overlays/testing.yml" || sources[0].Layer != LayerOverlay || sources[0].Kind != SourceKindFilesystem {
|
||||
t.Fatalf("native NoSource overlay sources = %#v", sources)
|
||||
}
|
||||
context, ok := loaded.Inspection.SourceContext("source-1")
|
||||
if !ok || context.BaseDir != "overlays" {
|
||||
t.Fatalf("native overlay resource context = %#v, %v", context, ok)
|
||||
}
|
||||
data, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, loaded.Config.Resource))
|
||||
if err != nil || string(data) != "native asset\n" {
|
||||
t.Fatalf("native overlay resource = %q, %v", data, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAppliesSelectedOverlayAfterBaseAndBeforeDefaults(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
|
||||
@@ -176,8 +176,7 @@ func Mapping(label string, values map[string]any) Source {
|
||||
|
||||
// NoSource selects an absent input for defaults and resolution. It creates no
|
||||
// source record and has no main declarations to traverse when imports are enabled.
|
||||
// An explicit filesystem context may be supplied; active overlays are not
|
||||
// available for absent inputs.
|
||||
// An active overlay requires explicit filesystem context.
|
||||
func NoSource() Source {
|
||||
return Source{kind: sourceNone}
|
||||
}
|
||||
|
||||
@@ -51,10 +51,14 @@ func contextsForAcquiredSources(source Source, context *FilesystemContext, baseS
|
||||
value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil)
|
||||
return nil, &value
|
||||
}
|
||||
basePath, err = canonicalExistingPath(context.BaseDir)
|
||||
if err != nil {
|
||||
value := importDiagnostic(CodeImportInvalid, "import base directory could not be canonicalized", nil)
|
||||
return nil, &value
|
||||
if source.kind == sourceNone && context.BaseDir == "" {
|
||||
basePath = rootPath
|
||||
} else {
|
||||
basePath, err = canonicalExistingPath(context.BaseDir)
|
||||
if err != nil {
|
||||
value := importDiagnostic(CodeImportInvalid, "import base directory could not be canonicalized", nil)
|
||||
return nil, &value
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, nil
|
||||
|
||||
Reference in New Issue
Block a user