Reuse owned assembly across configuration layers

This commit is contained in:
2026-09-29 17:13:14 +00:00
parent 8d9a3a8dfa
commit de28718941
10 changed files with 278 additions and 92 deletions

View File

@@ -42,6 +42,68 @@ func BenchmarkOverlayRecursiveMappings(b *testing.B) {
}
}
func BenchmarkOverlayManyApplications(b *testing.B) {
for _, count := range []int{32, 64, 128} {
inputs := make([]*document.Node, count)
for index := range inputs {
inputs[index] = benchmarkCatalog("source-2", index, 1)
}
benchmarkOverlayApplications(b, count, inputs)
}
}
func BenchmarkOverlayRepeatedReplacement(b *testing.B) {
for _, count := range []int{32, 64, 128} {
inputs := make([]*document.Node, count)
for index := range inputs {
location := document.Location{SourceID: "source-2"}
value := &document.Node{Kind: document.String, Text: "replacement", Location: location}
if index%2 == 0 {
value = &document.Node{Kind: document.Mapping, Location: location, Mapping: []document.Entry{{
Key: "enabled", Value: &document.Node{Kind: document.Boolean, Text: "false", Location: location},
}}}
}
inputs[index] = &document.Node{Kind: document.Mapping, Location: location, Mapping: []document.Entry{{
Key: "catalog", Value: &document.Node{Kind: document.Mapping, Location: location, Mapping: []document.Entry{{
Key: "item-0", Value: value,
}}},
}}}
}
benchmarkOverlayApplications(b, count, inputs)
}
}
func benchmarkOverlayApplications(b *testing.B, count int, inputs []*document.Node) {
base, diagnostics := composeDocuments(benchmarkCatalog("source-1", 0, 1))
if len(diagnostics) != 0 {
b.Fatal(diagnostics)
}
for _, mode := range []string{"owned", "copy-preserving"} {
b.Run(mode+"/"+strconv.Itoa(count), func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for range b.N {
if mode == "owned" {
assembly := newAssemblyAccumulator(cloneComposedDocument(base))
for _, input := range inputs {
if diagnostics := assembly.applyOverlay(input, nil, nil, LayerOverlay); len(diagnostics) != 0 {
b.Fatal(diagnostics)
}
}
continue
}
current := base
for _, input := range inputs {
current, diagnostics = overlayDocuments(current, input)
if len(diagnostics) != 0 {
b.Fatal(diagnostics)
}
}
}
})
}
}
func benchmarkCatalog(source string, first, width int) *document.Node {
location := document.Location{SourceID: source}
entries := make([]document.Entry, width)

View File

@@ -15,6 +15,17 @@ type composedDocument struct {
inputNodes int
}
// assemblyAccumulator owns one mutable working document. Acquired trees held by
// history applications are separate from its root and remain unchanged.
type assemblyAccumulator struct {
current *composedDocument
index mappingIndex
}
func newAssemblyAccumulator(owned *composedDocument) *assemblyAccumulator {
return &assemblyAccumulator{current: owned}
}
// cloneComposedDocument detaches mutable composition and provenance state;
// history application trees are immutable and retain their existing sharing.
func cloneComposedDocument(base *composedDocument) *composedDocument {

View File

@@ -51,13 +51,14 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
After: valueSnapshot(working, path, r.secrets), Origin: Origin{Layer: LayerDefault},
})
}
replayIndex := new(mappingIndex)
for _, application := range r.applications {
operationPath, relevant := applicationOperationPath(application, path)
var before ValueSnapshot
if relevant {
before = valueSnapshot(working, path, r.secrets)
}
applyHistoryApplication(&working, application)
applyHistoryApplication(&working, application, replayIndex)
if !relevant {
continue
}

View File

@@ -83,15 +83,15 @@ func pathsIntersect(left, right Path) bool {
return pathContains(left, right) || pathContains(right, left)
}
func applyHistoryApplication(root **document.Node, application historyApplication) {
func applyHistoryApplication(root **document.Node, application historyApplication, index *mappingIndex) {
if application.path.String() == "" {
mergeHistoryDocument(root, application.value)
mergeHistoryDocument(root, application.value, index)
return
}
mergeHistoryAtPath(root, application.path, application.value)
mergeHistoryAtPath(root, application.path, application.value, index)
}
func mergeHistoryDocument(target **document.Node, incoming *document.Node) {
func mergeHistoryDocument(target **document.Node, incoming *document.Node, index *mappingIndex) {
if incoming == nil {
return
}
@@ -100,39 +100,39 @@ func mergeHistoryDocument(target **document.Node, incoming *document.Node) {
return
}
for _, entry := range incoming.Mapping {
position := documentEntryIndex((*target).Mapping, entry.Key)
position := index.find(*target, entry.Key)
if position < 0 {
cloned := entry
cloned.Value = document.Clone(entry.Value)
(*target).Mapping = append((*target).Mapping, cloned)
index.append(*target, cloned)
continue
}
mergeHistoryDocument(&(*target).Mapping[position].Value, entry.Value)
mergeHistoryDocument(&(*target).Mapping[position].Value, entry.Value, index)
}
}
func mergeHistoryAtPath(root **document.Node, path Path, incoming *document.Node) {
func mergeHistoryAtPath(root **document.Node, path Path, incoming *document.Node, index *mappingIndex) {
if root == nil || *root == nil || len(path.segments) == 0 {
mergeHistoryDocument(root, incoming)
mergeHistoryDocument(root, incoming, index)
return
}
current := *root
for index, segment := range path.segments {
for segmentIndex, segment := range path.segments {
if current.Kind != document.Mapping || segment.kind == pathSegmentIndex {
return
}
position := documentEntryIndex(current.Mapping, segment.key)
if index == len(path.segments)-1 {
position := index.find(current, segment.key)
if segmentIndex == len(path.segments)-1 {
if position < 0 {
current.Mapping = append(current.Mapping, document.Entry{Key: segment.key, Value: document.Clone(incoming)})
index.append(current, document.Entry{Key: segment.key, Value: document.Clone(incoming)})
} else {
mergeHistoryDocument(&current.Mapping[position].Value, incoming)
mergeHistoryDocument(&current.Mapping[position].Value, incoming, index)
}
return
}
if position < 0 {
child := &document.Node{Kind: document.Mapping}
current.Mapping = append(current.Mapping, document.Entry{Key: segment.key, Value: child})
index.append(current, document.Entry{Key: segment.key, Value: child})
current = child
} else {
current = current.Mapping[position].Value

View File

@@ -47,9 +47,10 @@ func applyInputLayers[T any](source Source, options Options[T], input assembledI
if len(diagnostics) != 0 {
return input, diagnostics
}
assembly := newAssemblyAccumulator(composition)
if activeOverlay(options.Overlay) {
composition, input.sources, diagnostics = loadSelectedOverlayDocument(
source, options.Filesystem, input.parsed, composition, input.sources,
input.sources, diagnostics = loadSelectedOverlayInto(
source, options.Filesystem, input.parsed, assembly, input.sources,
options.Overlay, options.Definition.Version, defaultImportLimits(),
)
if len(diagnostics) != 0 {
@@ -57,15 +58,15 @@ func applyInputLayers[T any](source Source, options Options[T], input assembledI
}
}
if len(options.Overrides) != 0 {
composition, input.sources, diagnostics = applyOverrides(
composition, input.sources, options.Overrides, options.Definition.Version, defaultImportLimits(),
input.sources, diagnostics = assembly.applyOverrides(
input.sources, options.Overrides, options.Definition.Version, defaultImportLimits(),
)
if len(diagnostics) != 0 {
return input, diagnostics
}
}
input.composition = composition
input.document = composition.root
input.composition = assembly.current
input.document = assembly.current.root
return input, nil
}

View File

@@ -18,24 +18,41 @@ type displacedDefinition struct {
}
func overlayDocuments(base *composedDocument, overlay *document.Node) (*composedDocument, []Diagnostic) {
if base == nil || base.root == nil || base.root.Kind != document.Mapping || overlay == nil || overlay.Kind != document.Mapping {
return nil, []Diagnostic{{
Code: CodeOverlayInvalid, Severity: SeverityError,
Message: "overlay requires mapping document roots",
}}
if base == nil {
return nil, invalidOverlayDocument()
}
result := cloneComposedDocument(base)
mergeOverlayOrigin(result.origins, Path{}, overlay.Location, LayerOverlay)
mergeOverlayMappings(result, result.root, overlay, Path{}, LayerOverlay, true)
result.applications = append(result.applications, historyApplication{layer: LayerOverlay, value: overlay})
return result, nil
assembly := newAssemblyAccumulator(cloneComposedDocument(base))
if diagnostics := assembly.applyOverlay(overlay, nil, nil, LayerOverlay); len(diagnostics) != 0 {
return nil, diagnostics
}
return assembly.current, nil
}
func mergeOverlayMappings(result *composedDocument, target, overlay *document.Node, current Path, layer Layer, recordReplacements bool) {
mergeOverlayMappingsIndexed(result, target, overlay, current, layer, recordReplacements, new(mappingIndex))
func invalidOverlayDocument() []Diagnostic {
return []Diagnostic{{
Code: CodeOverlayInvalid, Severity: SeverityError,
Message: "overlay requires mapping document roots",
}}
}
func mergeOverlayMappingsIndexed(result *composedDocument, target, overlay *document.Node, current Path, layer Layer, recordReplacements bool, index *mappingIndex) {
func (a *assemblyAccumulator) applyOverlay(overlay *document.Node, incomingOrigins map[string]Origin, applications []historyApplication, layer Layer) []Diagnostic {
if a == nil || a.current == nil || a.current.root == nil || a.current.root.Kind != document.Mapping || overlay == nil || overlay.Kind != document.Mapping {
return invalidOverlayDocument()
}
mergeOriginContribution(a.current.origins, Path{}, incomingOrigin(incomingOrigins, Path{}, overlay, layer))
mergeOverlayMappings(a.current, a.current.root, overlay, Path{}, layer, true, incomingOrigins, &a.index)
if len(applications) == 0 {
a.current.applications = append(a.current.applications, historyApplication{layer: layer, value: overlay})
} else {
for _, application := range applications {
application.layer = layer
a.current.applications = append(a.current.applications, application)
}
}
return nil
}
func mergeOverlayMappings(result *composedDocument, target, overlay *document.Node, current Path, layer Layer, recordReplacements bool, incomingOrigins map[string]Origin, index *mappingIndex) {
for _, incoming := range overlay.Mapping {
path := current.Key(incoming.Key)
position := index.find(target, incoming.Key)
@@ -43,27 +60,27 @@ func mergeOverlayMappingsIndexed(result *composedDocument, target, overlay *docu
cloned := incoming
cloned.Value = document.Clone(incoming.Value)
index.append(target, cloned)
collectOverlayOrigins(result.origins, incoming.Value, path, layer)
collectOverlayOriginsFrom(result.origins, incomingOrigins, incoming.Value, path, layer)
continue
}
existing := target.Mapping[position].Value
if existing.Kind == document.Mapping && incoming.Value.Kind == document.Mapping {
mergeOverlayOrigin(result.origins, path, incoming.Value.Location, layer)
mergeOverlayMappingsIndexed(result, existing, incoming.Value, path, layer, recordReplacements, index)
mergeOriginContribution(result.origins, path, incomingOrigin(incomingOrigins, path, incoming.Value, layer))
mergeOverlayMappings(result, existing, incoming.Value, path, layer, recordReplacements, incomingOrigins, index)
continue
}
displaced := displacedOrigins(existing, result.origins, path)
previous := cloneOrigin(result.origins[path.String()])
deleteOriginSubtree(result.origins, path)
deleteOriginSubtree(result.origins, path, existing)
target.Mapping[position] = incoming
target.Mapping[position].Value = document.Clone(incoming.Value)
collectOverlayOrigins(result.origins, incoming.Value, path, layer)
collectOverlayOriginsFrom(result.origins, incomingOrigins, incoming.Value, path, layer)
if recordReplacements {
result.overlays = append(result.overlays, overlayRecord{
change: Change{
Path: path.clone(), Origin: overlayOrigin(layer, incoming.Value.Location),
Path: path.clone(), Origin: incomingOrigin(incomingOrigins, path, incoming.Value, layer),
Previous: previousOrigins(previous),
},
displaced: displaced,
@@ -73,27 +90,44 @@ func mergeOverlayMappingsIndexed(result *composedDocument, target, overlay *docu
}
func collectOverlayOrigins(origins map[string]Origin, node *document.Node, path Path, layer Layer) {
origins[path.String()] = overlayOrigin(layer, node.Location)
collectOverlayOriginsFrom(origins, nil, node, path, layer)
}
func collectOverlayOriginsFrom(origins, incomingOrigins map[string]Origin, node *document.Node, path Path, layer Layer) {
origins[path.String()] = incomingOrigin(incomingOrigins, path, node, layer)
switch node.Kind {
case document.Mapping:
for _, entry := range node.Mapping {
collectOverlayOrigins(origins, entry.Value, path.Key(entry.Key), layer)
collectOverlayOriginsFrom(origins, incomingOrigins, entry.Value, path.Key(entry.Key), layer)
}
case document.Sequence:
for index, child := range node.Sequence {
collectOverlayOrigins(origins, child, path.Index(index), layer)
collectOverlayOriginsFrom(origins, incomingOrigins, child, path.Index(index), layer)
}
}
}
func mergeOverlayOrigin(origins map[string]Origin, path Path, location document.Location, layer Layer) {
mergeOriginContribution(origins, path, overlayOrigin(layer, location))
}
func mergeOriginContribution(origins map[string]Origin, path Path, incoming Origin) {
pointer := path.String()
origin := cloneOrigin(origins[pointer])
origin.Layer = layer
origin.Locations = append(origin.Locations, publicLocation(location))
origin := origins[pointer]
origin.Layer = incoming.Layer
origin.Locations = append(origin.Locations, incoming.Locations...)
origins[pointer] = origin
}
func incomingOrigin(origins map[string]Origin, path Path, node *document.Node, layer Layer) Origin {
if origin, ok := origins[path.String()]; ok {
origin = cloneOrigin(origin)
origin.Layer = layer
return origin
}
return overlayOrigin(layer, node.Location)
}
func overlayOrigin(layer Layer, location document.Location) Origin {
return Origin{Layer: layer, Locations: []Location{publicLocation(location)}}
}
@@ -133,11 +167,16 @@ func collectDisplacedOrigins(displaced *[]displacedDefinition, node *document.No
}
}
func deleteOriginSubtree(origins map[string]Origin, path Path) {
prefix := path.String()
for pointer := range origins {
if pointer == prefix || strings.HasPrefix(pointer, prefix+"/") {
delete(origins, pointer)
func deleteOriginSubtree(origins map[string]Origin, path Path, node *document.Node) {
delete(origins, path.String())
switch node.Kind {
case document.Mapping:
for _, entry := range node.Mapping {
deleteOriginSubtree(origins, path.Key(entry.Key), entry.Value)
}
case document.Sequence:
for index, child := range node.Sequence {
deleteOriginSubtree(origins, path.Index(index), child)
}
}
}

View File

@@ -76,3 +76,44 @@ to_scalar: scalar
t.Fatal("overlay result aliases an input document")
}
}
func TestOwnedOverlayKeepsCompositeContributorsAndAcquiredInputs(t *testing.T) {
baseInput := parseCompositionDocument(t, "source-1", "catalog:\n base: true\n")
base, diagnostics := composeDocuments(baseInput)
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
first := parseCompositionDocument(t, "source-2", "catalog:\n first: true\n")
second := parseCompositionDocument(t, "source-3", "catalog:\n second: true\n")
firstBefore, secondBefore := document.Clone(first), document.Clone(second)
incoming, diagnostics := composeDocuments(first, second)
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
assembly := newAssemblyAccumulator(base)
if diagnostics := assembly.applyOverlay(incoming.root, incoming.origins, incoming.applications, LayerOverlay); len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
for _, key := range []string{"base", "first", "second"} {
if _, ok := documentAtPath(assembly.current.root, Keys("catalog", key)); !ok {
t.Fatalf("catalog is missing %q", key)
}
}
origin := assembly.current.origins[Keys("catalog").String()]
if got, want := origin.Locations, []Location{{SourceID: "source-1", Line: 2, Column: 3}, {SourceID: "source-2", Line: 2, Column: 3}, {SourceID: "source-3", Line: 2, Column: 3}}; !reflect.DeepEqual(got, want) {
t.Fatalf("catalog origin locations = %#v, want %#v", got, want)
}
if len(assembly.current.applications) != 3 {
t.Fatalf("history applications = %d, want three source contributions", len(assembly.current.applications))
}
for _, application := range assembly.current.applications[1:] {
if application.layer != LayerOverlay {
t.Fatalf("incoming application layer = %q", application.layer)
}
}
assembly.current.root.Mapping[0].Value.Mapping[1].Value.Text = "false"
if !reflect.DeepEqual(first, firstBefore) || !reflect.DeepEqual(second, secondBefore) ||
!reflect.DeepEqual(first, assembly.current.applications[1].value) || !reflect.DeepEqual(second, assembly.current.applications[2].value) {
t.Fatal("working tree mutation changed an acquired input")
}
}

View File

@@ -11,31 +11,42 @@ func applyOverrides(base *composedDocument, sources []SourceInfo, overrides []Ov
if len(overrides) == 0 {
return base, cloneSourceInfos(sources), nil
}
prepared, allSources, diagnostics := prepareOverrides(base, sources, overrides, rule, limits)
assembly := newAssemblyAccumulator(cloneComposedDocument(base))
allSources, diagnostics := assembly.applyOverrides(sources, overrides, rule, limits)
if len(diagnostics) != 0 {
return nil, allSources, diagnostics
}
result := cloneComposedDocument(base)
for _, override := range prepared {
applyPreparedOverride(result, override)
result.inputBytes += override.bytes
result.inputNodes += override.nodes
}
return result, allSources, nil
return assembly.current, allSources, nil
}
func applyPreparedOverride(result *composedDocument, override preparedOverride) {
func (a *assemblyAccumulator) applyOverrides(sources []SourceInfo, overrides []Override, rule *VersionRule, limits importLimits) ([]SourceInfo, []Diagnostic) {
if len(overrides) == 0 {
return cloneSourceInfos(sources), nil
}
prepared, allSources, diagnostics := prepareOverrides(a.current, sources, overrides, rule, limits)
if len(diagnostics) != 0 {
return allSources, diagnostics
}
for _, override := range prepared {
applyPreparedOverride(a.current, override, &a.index)
a.current.inputBytes += override.bytes
a.current.inputNodes += override.nodes
}
return allSources, nil
}
func applyPreparedOverride(result *composedDocument, override preparedOverride, index *mappingIndex) {
location := document.Location{SourceID: override.source.ID}
mergeOverlayOrigin(result.origins, Path{}, location, LayerCLI)
current := result.root
currentPath := Path{}
for index, segment := range override.path.segments {
for segmentIndex, segment := range override.path.segments {
path := currentPath.Key(segment.key)
position := documentEntryIndex(current.Mapping, segment.key)
if index < len(override.path.segments)-1 {
position := index.find(current, segment.key)
if segmentIndex < len(override.path.segments)-1 {
if position < 0 {
child := &document.Node{Kind: document.Mapping, Location: location, Mapping: []document.Entry{}}
current.Mapping = append(current.Mapping, document.Entry{Key: segment.key, KeyLocation: location, ValueLocation: location, Value: child})
index.append(current, document.Entry{Key: segment.key, KeyLocation: location, ValueLocation: location, Value: child})
collectOverlayOrigins(result.origins, child, path, LayerCLI)
current = child
} else {
@@ -50,16 +61,16 @@ func applyPreparedOverride(result *composedDocument, override preparedOverride)
var displaced []displacedDefinition
if position < 0 {
incoming := document.Clone(override.value)
current.Mapping = append(current.Mapping, document.Entry{Key: segment.key, KeyLocation: location, ValueLocation: location, Value: incoming})
index.append(current, document.Entry{Key: segment.key, KeyLocation: location, ValueLocation: location, Value: incoming})
collectOverlayOrigins(result.origins, incoming, path, LayerCLI)
} else {
existing := current.Mapping[position].Value
displaced = affectedOverrideOrigins(existing, override.value, result.origins, path)
displaced = affectedOverrideOrigins(existing, override.value, result.origins, path, index)
if existing.Kind == document.Mapping && override.value.Kind == document.Mapping {
mergeOverlayOrigin(result.origins, path, location, LayerCLI)
mergeOverlayMappings(result, existing, override.value, path, LayerCLI, false)
mergeOverlayMappings(result, existing, override.value, path, LayerCLI, false, nil, index)
} else {
deleteOriginSubtree(result.origins, path)
deleteOriginSubtree(result.origins, path, existing)
current.Mapping[position] = document.Entry{Key: segment.key, KeyLocation: location, ValueLocation: location, Value: document.Clone(override.value)}
collectOverlayOrigins(result.origins, current.Mapping[position].Value, path, LayerCLI)
}
@@ -74,18 +85,18 @@ func applyPreparedOverride(result *composedDocument, override preparedOverride)
}
}
func affectedOverrideOrigins(existing, incoming *document.Node, origins map[string]Origin, path Path) []displacedDefinition {
func affectedOverrideOrigins(existing, incoming *document.Node, origins map[string]Origin, path Path, index *mappingIndex) []displacedDefinition {
displaced := []displacedDefinition{{path: path.clone(), origin: cloneOrigin(origins[path.String()])}}
if existing.Kind == document.Mapping && incoming.Kind == document.Mapping {
for _, entry := range incoming.Mapping {
position := documentEntryIndex(existing.Mapping, entry.Key)
position := index.find(existing, entry.Key)
if position < 0 {
continue
}
childPath := path.Key(entry.Key)
childExisting := existing.Mapping[position].Value
if childExisting.Kind == document.Mapping && entry.Value.Kind == document.Mapping {
displaced = append(displaced, affectedOverrideOrigins(childExisting, entry.Value, origins, childPath)...)
displaced = append(displaced, affectedOverrideOrigins(childExisting, entry.Value, origins, childPath, index)...)
} else {
displaced = append(displaced, displacedOrigins(childExisting, origins, childPath)...)
}

View File

@@ -32,9 +32,18 @@ func validOverlayName(name string) bool {
}
func loadSelectedOverlayDocument(source Source, context *FilesystemContext, main parsedSource, base *composedDocument, sources []SourceInfo, name string, rule *VersionRule, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) {
assembly := newAssemblyAccumulator(cloneComposedDocument(base))
allSources, diagnostics := loadSelectedOverlayInto(source, context, main, assembly, sources, name, rule, limits)
if len(diagnostics) != 0 {
return nil, allSources, diagnostics
}
return assembly.current, allSources, nil
}
func loadSelectedOverlayInto(source Source, context *FilesystemContext, main parsedSource, base *assemblyAccumulator, sources []SourceInfo, name string, rule *VersionRule, limits importLimits) ([]SourceInfo, []Diagnostic) {
rootPath, _, _, diagnostic := importFilesystem(source, context, main)
if diagnostic != nil {
return nil, cloneSourceInfos(sources), []Diagnostic{*diagnostic}
return cloneSourceInfos(sources), []Diagnostic{*diagnostic}
}
overlayPath := filepath.Join(rootPath, "overlays", name+".yml")
canonical, err := canonicalExistingPath(overlayPath)
@@ -46,25 +55,25 @@ func loadSelectedOverlayDocument(source Source, context *FilesystemContext, main
} else if errors.Is(err, fs.ErrPermission) {
code, message = CodeImportPermission, "selected overlay cannot be accessed"
}
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(code, message, nil)}
return cloneSourceInfos(sources), []Diagnostic{importDiagnostic(code, message, nil)}
}
if !containedPath(rootPath, canonical) {
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay resolves outside the configuration root", nil)}
return cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay resolves outside the configuration root", nil)}
}
for _, existing := range sources {
if existing.Path != "" && existing.Path == canonical {
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayRepeated, "selected overlay is already present in the base source graph", nil)}
return cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayRepeated, "selected overlay is already present in the base source graph", nil)}
}
}
if len(sources)+1 > limits.sources {
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayLimitExceeded, "selected overlay exceeds the source-count limit", nil)}
return cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayLimitExceeded, "selected overlay exceeds the source-count limit", nil)}
}
root, err := os.OpenRoot(rootPath)
if err != nil {
value := globFilesystemDiagnostic(err, "configuration root could not be opened for the selected overlay", Location{})
value.Primary = nil
return nil, cloneSourceInfos(sources), []Diagnostic{value}
return cloneSourceInfos(sources), []Diagnostic{value}
}
rootClosed := false
defer func() {
@@ -74,31 +83,31 @@ func loadSelectedOverlayDocument(source Source, context *FilesystemContext, main
}()
relative, err := filepath.Rel(rootPath, canonical)
if err != nil || filepath.IsAbs(relative) || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return nil, cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay could not be addressed inside the configuration root", nil)}
return cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay could not be addressed inside the configuration root", nil)}
}
sourceID := "source-" + strconv.Itoa(len(sources)+1)
overlayLocation := &Location{SourceID: sourceID}
overlaySource := SourceInfo{ID: sourceID, Label: filepath.ToSlash(filepath.Join("overlays", name+".yml")), Path: canonical, Layer: LayerOverlay}
sources = append(cloneSourceInfos(sources), overlaySource)
data, diagnostic := readOverlayFile(root, relative, sourceID, limits.bytes-base.inputBytes)
data, diagnostic := readOverlayFile(root, relative, sourceID, limits.bytes-base.current.inputBytes)
if diagnostic != nil {
return nil, sources, []Diagnostic{*diagnostic}
return sources, []Diagnostic{*diagnostic}
}
overlayRoot, issues := yamladapter.Parse(bytes.NewReader(data), sourceID, yamladapter.Limits{Depth: maxDocumentDepth, Nodes: maxDocumentNodes})
if len(issues) != 0 {
return nil, sources, parserDiagnostics(sourceID, issues)
return sources, parserDiagnostics(sourceID, issues)
}
nodeCount := countInputNodes(overlayRoot)
if base.inputNodes+nodeCount > limits.nodes {
return nil, sources, []Diagnostic{importDiagnostic(CodeOverlayLimitExceeded, "selected overlay exceeds the aggregate node limit", overlayLocation)}
if base.current.inputNodes+nodeCount > limits.nodes {
return sources, []Diagnostic{importDiagnostic(CodeOverlayLimitExceeded, "selected overlay exceeds the aggregate node limit", overlayLocation)}
}
if diagnostics := overlayDirectiveDiagnostics(overlayRoot, Path{}); len(diagnostics) != 0 {
return nil, sources, diagnostics
return sources, diagnostics
}
if versionNode := fragmentVersionClaim(overlayRoot, rule); versionNode != nil {
location := publicLocation(versionNode.Location)
return nil, sources, []Diagnostic{{
return sources, []Diagnostic{{
Code: CodeOverlayVersion, Severity: SeverityError, Path: rule.Path.clone(),
Message: "overlays cannot contribute to or replace the schema-version path", Primary: &location,
}}
@@ -106,15 +115,15 @@ func loadSelectedOverlayDocument(source Source, context *FilesystemContext, main
closeErr := root.Close()
rootClosed = true
if closeErr != nil {
return nil, sources, []Diagnostic{importDiagnostic(CodeImportRead, "configuration root could not be closed after reading the selected overlay", nil)}
return sources, []Diagnostic{importDiagnostic(CodeImportRead, "configuration root could not be closed after reading the selected overlay", nil)}
}
overlayd, diagnostics := overlayDocuments(base, overlayRoot)
if overlayd != nil {
overlayd.inputBytes = base.inputBytes + int64(len(data))
overlayd.inputNodes = base.inputNodes + nodeCount
if diagnostics := base.applyOverlay(overlayRoot, nil, nil, LayerOverlay); len(diagnostics) != 0 {
return sources, diagnostics
}
return overlayd, sources, diagnostics
base.current.inputBytes += int64(len(data))
base.current.inputNodes += nodeCount
return sources, nil
}
func readOverlayFile(root *os.Root, relative, sourceID string, remaining int64) ([]byte, *Diagnostic) {