Expose layered configuration loading and inspection
This commit is contained in:
@@ -38,7 +38,7 @@ func (r *Inspection) WriteExplanation(output io.Writer, path Path) error {
|
||||
rendered.WriteString(" - representation: ")
|
||||
if change.Origin.Operation == ChangeDecoding {
|
||||
rendered.WriteString("input to canonical typed\n")
|
||||
} else if change.Origin.Layer == LayerBase || change.Origin.Layer == LayerOverlay || change.Origin.Layer == LayerCLI {
|
||||
} else if change.Origin.Layer == LayerBase || change.Origin.Layer == LayerUnderlay || change.Origin.Layer == LayerOverlay || change.Origin.Layer == LayerCLI {
|
||||
rendered.WriteString("policy-validated input\n")
|
||||
} else {
|
||||
rendered.WriteString("canonical typed\n")
|
||||
|
||||
@@ -261,6 +261,9 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
|
||||
label = s.access.label + "/" + canonical
|
||||
}
|
||||
info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Layer: LayerBase, Kind: kind, ImportedFrom: &location}
|
||||
if kind == SourceKindFilesystem {
|
||||
info.namespace = s.access.token
|
||||
}
|
||||
s.sources = append(s.sources, info)
|
||||
data, skipped, diagnostic := s.readImport(canonical, sourceID, declaration.optional)
|
||||
if diagnostic != nil {
|
||||
|
||||
50
internal/engine/layered.go
Normal file
50
internal/engine/layered.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package engine
|
||||
|
||||
import "os"
|
||||
|
||||
// Input selects one source and its independent import policy.
|
||||
type Input struct {
|
||||
Source Source
|
||||
Imports bool
|
||||
Filesystem *FilesystemContext
|
||||
}
|
||||
|
||||
// Underlay contains strictly composed contributions at one precedence tier.
|
||||
type Underlay struct{ Inputs []Input }
|
||||
|
||||
// LayeredOptions selects lower-priority tiers and final application rules.
|
||||
type LayeredOptions[T any] struct {
|
||||
Definition Definition[T]
|
||||
Underlays []Underlay
|
||||
Overlay string
|
||||
OverlayFilesystem *FilesystemContext
|
||||
Overrides []Override
|
||||
}
|
||||
|
||||
// LoadLayered loads a main input and lower-priority grouped inputs through one
|
||||
// final decode, resolution, and validation lifecycle.
|
||||
func LoadLayered[T any](main Input, secrets SecretBinding, supplied ...LayeredOptions[T]) (*Loaded[T], error) {
|
||||
if len(supplied) > 1 {
|
||||
return nil, (&diagnosticAccumulator{diagnostics: []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "LoadLayered accepts at most one options value")}}).loadError()
|
||||
}
|
||||
var options LayeredOptions[T]
|
||||
if len(supplied) != 0 {
|
||||
options = supplied[0]
|
||||
}
|
||||
request := loadRequest[T]{
|
||||
main: loadInput{source: main.Source, imports: main.Imports, filesystem: main.Filesystem},
|
||||
definition: options.Definition, overlay: options.Overlay,
|
||||
overlayFilesystem: options.OverlayFilesystem,
|
||||
overrides: append([]Override(nil), options.Overrides...),
|
||||
underlays: make([]loadGroup, len(options.Underlays)),
|
||||
}
|
||||
for index, underlay := range options.Underlays {
|
||||
request.underlays[index].inputs = make([]loadInput, len(underlay.Inputs))
|
||||
for inputIndex, input := range underlay.Inputs {
|
||||
request.underlays[index].inputs[inputIndex] = loadInput{
|
||||
source: input.Source, imports: input.Imports, filesystem: input.Filesystem,
|
||||
}
|
||||
}
|
||||
}
|
||||
return loadGroupedWithSecretLookup(request, secrets, os.LookupEnv)
|
||||
}
|
||||
@@ -182,6 +182,7 @@ type SourceInfo struct {
|
||||
Kind SourceKind
|
||||
UnderlayIndex int
|
||||
ImportedFrom *Location
|
||||
namespace *namespaceToken
|
||||
}
|
||||
|
||||
// Diagnostic is a safe structured problem report. Message and source labels must
|
||||
|
||||
@@ -84,7 +84,9 @@ func loadSelectedOverlayWithAccess(access *filesystemAccess, base *assemblyAccum
|
||||
return cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay resolves outside the configuration root", nil)}
|
||||
}
|
||||
for _, existing := range sources {
|
||||
if existing.Path != "" && existing.Path == canonical {
|
||||
if existing.Path != "" && existing.Path == canonical &&
|
||||
((access.native == nil && existing.Kind == SourceKindFile) ||
|
||||
(access.native != nil && existing.Kind == SourceKindFilesystem && existing.namespace == access.token)) {
|
||||
return cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayRepeated, "selected overlay is already present in the base source graph", nil)}
|
||||
}
|
||||
}
|
||||
@@ -105,6 +107,9 @@ func loadSelectedOverlayWithAccess(access *filesystemAccess, base *assemblyAccum
|
||||
label = access.label + "/" + canonical
|
||||
}
|
||||
overlaySource := SourceInfo{ID: sourceID, Label: label, Path: canonical, Layer: LayerOverlay, Kind: kind}
|
||||
if kind == SourceKindFilesystem {
|
||||
overlaySource.namespace = access.token
|
||||
}
|
||||
sources = append(cloneSourceInfos(sources), overlaySource)
|
||||
data, diagnostic := readOverlayFile(access, canonical, sourceID, limits.bytes-base.current.inputBytes)
|
||||
if diagnostic != nil {
|
||||
|
||||
@@ -238,6 +238,7 @@ func readSource(source Source, sourceID string, limits sourceLimits) (sourceRead
|
||||
case sourceFilesystem:
|
||||
access := newNativeAccess(source.namespace.filesystem, ".", source.namespace.token)
|
||||
info.Path = source.path
|
||||
info.namespace = source.namespace.token
|
||||
canonical, err := access.canonical(source.path)
|
||||
if err != nil {
|
||||
return sourceRead{info: info}, []Diagnostic{classifySourceError(err, sourceID)}
|
||||
|
||||
Reference in New Issue
Block a user