Complete historical inventory and query services
This commit is contained in:
@@ -1,20 +1,16 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
)
|
||||
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
|
||||
// Metadata provides detached effective presence/origin and chronological history
|
||||
// queries for a load. Its zero value contains no paths.
|
||||
type Metadata struct {
|
||||
effectivePaths map[string]Path
|
||||
historicalPaths map[string][]Path
|
||||
fields map[string]FieldInfo
|
||||
history map[string][]Change
|
||||
inheritance []inheritanceOperation
|
||||
inheritanceIndex *inheritanceHistoryIndex
|
||||
historyModel *historyModel
|
||||
}
|
||||
|
||||
// Lookup returns information for an effective path. Unknown and invalid paths
|
||||
@@ -77,18 +73,7 @@ func (m Metadata) History(path Path) []Change {
|
||||
// a path remains effective. Enumeration exposes no configuration values and does
|
||||
// not perform another load or resolve secrets.
|
||||
func (m Metadata) HistoricalPaths() []Path {
|
||||
pointers := make([]string, 0, len(m.historicalPaths))
|
||||
for pointer := range m.historicalPaths {
|
||||
pointers = append(pointers, pointer)
|
||||
}
|
||||
sort.Strings(pointers)
|
||||
paths := make([]Path, 0, len(pointers))
|
||||
for _, pointer := range pointers {
|
||||
// The index contains only canonical pointers produced by Path.String.
|
||||
path, _ := ParsePath(pointer)
|
||||
paths = append(paths, path)
|
||||
}
|
||||
return paths
|
||||
return m.historyModel.historicalPaths()
|
||||
}
|
||||
|
||||
// Textual pointers are lookup keys, but explicit key/index kinds remain part of
|
||||
@@ -115,54 +100,75 @@ func matchesPathShape(query, candidate Path) bool {
|
||||
}
|
||||
|
||||
func (m Metadata) hasHistoricalPath(path Path) bool {
|
||||
if !path.valid() {
|
||||
return false
|
||||
}
|
||||
for _, candidate := range m.historicalPaths[path.String()] {
|
||||
if matchesPathShape(path, candidate) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return m.historyModel.hasObservedShape(path)
|
||||
}
|
||||
|
||||
func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []historyApplication, earlier ...*document.Node) {
|
||||
m.effectivePaths = make(map[string]Path)
|
||||
m.historicalPaths = make(map[string][]Path)
|
||||
addHistorical := func(path Path, pointer string) {
|
||||
for _, prior := range m.historicalPaths[pointer] {
|
||||
if matchesPathShape(path, prior) {
|
||||
return
|
||||
}
|
||||
finalizeMetadataPathsWithResolution(m, final, applications, nil, earlier...)
|
||||
}
|
||||
|
||||
func finalizeMetadataPathsWithResolution(m *Metadata, final *document.Node, applications []historyApplication, snapshots []resolutionSnapshot, earlier ...*document.Node) {
|
||||
finalizeEffectiveMetadataPaths(m, final)
|
||||
builder := newHistoryBuilder()
|
||||
if len(earlier) != 0 {
|
||||
builder.model.baseline = earlier[0]
|
||||
}
|
||||
builder.model.final = final
|
||||
for index, tree := range earlier {
|
||||
phase := historyPhaseSources
|
||||
switch index {
|
||||
case 0:
|
||||
phase = historyPhaseBaseline
|
||||
case 2:
|
||||
phase = historyPhasePreCLI
|
||||
case 3:
|
||||
phase = historyPhasePostCLI
|
||||
}
|
||||
m.historicalPaths[pointer] = append(m.historicalPaths[pointer], path.clone())
|
||||
builder.observeDocument(tree, phase)
|
||||
}
|
||||
walkDocument(final, Path{}, func(path Path, _ *document.Node) {
|
||||
pointer := path.String()
|
||||
m.effectivePaths[pointer] = path.clone()
|
||||
addHistorical(path, pointer)
|
||||
})
|
||||
add := func(path Path, _ *document.Node) {
|
||||
addHistorical(path, path.String())
|
||||
}
|
||||
for _, tree := range earlier {
|
||||
walkDocument(tree, Path{}, add)
|
||||
if final != nil {
|
||||
builder.observeDocument(final, historyPhasePostCLI)
|
||||
}
|
||||
for _, application := range applications {
|
||||
walkHistoryApplication(application, func(_ Path, effective Path, _ *document.Node) {
|
||||
add(effective, nil)
|
||||
builder.registerDocumentRoot(application.value)
|
||||
phase := historyPhaseSources
|
||||
if application.layer == LayerCLI {
|
||||
phase = historyPhaseCLI
|
||||
}
|
||||
payload := builder.nextPayload(phase)
|
||||
walkHistoryApplication(application, func(_, effective Path, node *document.Node) {
|
||||
builder.addInventoryObservation(effective, effective, payload, phase, node.Sensitive)
|
||||
})
|
||||
}
|
||||
for _, operation := range m.inheritance {
|
||||
payload := builder.nextPayload(historyPhaseInheritance)
|
||||
if operation.control != nil {
|
||||
add(operation.control.path, nil)
|
||||
builder.addInventoryObservation(operation.control.path, operation.control.path, payload, historyPhaseInheritance, false)
|
||||
continue
|
||||
}
|
||||
if operation.transfer != nil {
|
||||
walkDocument(operation.parent, operation.transfer.to, add)
|
||||
walkDocument(operation.effective, operation.transfer.to, add)
|
||||
if operation.transfer == nil {
|
||||
continue
|
||||
}
|
||||
builder.registerDocumentRoot(operation.parent)
|
||||
builder.registerDocumentRoot(operation.effective)
|
||||
for _, root := range []*document.Node{operation.parent, operation.effective} {
|
||||
walkDocument(root, operation.transfer.to, func(path Path, node *document.Node) {
|
||||
builder.addInventoryObservation(path, path, payload, historyPhaseInheritance, node.Sensitive)
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(snapshots) != 0 {
|
||||
if len(earlier) != 0 {
|
||||
builder.setScratch(earlier[len(earlier)-1])
|
||||
}
|
||||
builder.observeResolutionEvidence(snapshots)
|
||||
}
|
||||
m.historyModel = builder.freeze()
|
||||
m.inheritanceIndex = newInheritanceHistoryIndex(m.inheritance)
|
||||
}
|
||||
|
||||
func finalizeEffectiveMetadataPaths(m *Metadata, final *document.Node) {
|
||||
m.effectivePaths = effectivePathIndex(final)
|
||||
m.inheritanceIndex = newInheritanceHistoryIndex(m.inheritance)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user