166 lines
5.7 KiB
Go
166 lines
5.7 KiB
Go
package engine
|
|
|
|
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
|
|
// ValueSnapshot is a detached, deterministic YAML representation of one value.
|
|
// Present distinguishes absence or deletion from an explicit null value.
|
|
type ValueSnapshot struct {
|
|
Present bool
|
|
Redacted bool
|
|
YAML string
|
|
}
|
|
|
|
// ValueChange describes one explicit definition or audited domain operation.
|
|
type ValueChange struct {
|
|
Before ValueSnapshot
|
|
After ValueSnapshot
|
|
Origin Origin
|
|
Reasons []string
|
|
Dependencies []Path
|
|
}
|
|
|
|
// SecretReferenceInfo describes a successfully resolved effective reference.
|
|
// Name is the environment-variable name, never its value.
|
|
type SecretReferenceInfo struct {
|
|
Name string
|
|
}
|
|
|
|
// Explanation is a detached safe account of one effective or historical path.
|
|
type Explanation struct {
|
|
Path Path
|
|
Effective ValueSnapshot
|
|
Origin Origin
|
|
History []ValueChange
|
|
SecretReference *SecretReferenceInfo
|
|
}
|
|
|
|
// Explain returns safe effective and chronological values for a known final or
|
|
// historical path. Unknown and malformed paths return false.
|
|
func (r *Inspection) Explain(path Path) (Explanation, bool) {
|
|
if r == nil || r.final == nil || r.baseline == nil || !r.metadata.hasHistoricalPath(path) {
|
|
return Explanation{}, false
|
|
}
|
|
explanation := Explanation{Path: path.clone(), Effective: valueSnapshotWithLookup(r.final, path, r.secrets, r.immutableLookup)}
|
|
if info, ok := r.metadata.Lookup(path); ok {
|
|
explanation.Origin = cloneOrigin(info.Origin)
|
|
}
|
|
|
|
working := document.Clone(r.baseline)
|
|
if _, present := documentAtPath(working, path); present {
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
After: valueSnapshot(working, path, r.secrets), Origin: Origin{Layer: LayerDefault},
|
|
})
|
|
}
|
|
replayIndex := new(mappingIndex)
|
|
for _, application := range r.applications {
|
|
operationPath, relevant := applicationOperationPath(application, path, r.immutableLookup)
|
|
var before ValueSnapshot
|
|
if relevant {
|
|
before = valueSnapshot(working, path, r.secrets)
|
|
}
|
|
applyHistoryApplication(&working, application, replayIndex)
|
|
if !relevant {
|
|
continue
|
|
}
|
|
after := valueSnapshot(working, path, r.secrets)
|
|
if !before.Present && !after.Present {
|
|
continue
|
|
}
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
Before: before,
|
|
After: after,
|
|
Origin: historyApplicationOrigin(application, operationPath, r.immutableLookup),
|
|
})
|
|
}
|
|
appendInheritanceExplanation(&explanation, &working, path, r.inheritance, r.secrets)
|
|
|
|
// Intermediate input replacements do not permanently suppress defaults:
|
|
// decoding applies them to the final input tree. Show that reconciliation
|
|
// explicitly, including canonical scalar conversions, before domain changes.
|
|
decodedInput := r.input
|
|
decodedOrigins := r.inputOrigins
|
|
if r.preCLI != nil {
|
|
decodedInput = r.preCLI
|
|
decodedOrigins = r.preCLIOrigins
|
|
}
|
|
replayed, _ := documentAtPath(working, path)
|
|
decoded, _ := documentAtPathWithLookup(decodedInput, path, r.immutableLookup)
|
|
if !configurationValuesEqual(replayed, decoded) {
|
|
origin := cloneOrigin(decodedOrigins[path.String()])
|
|
origin.Operation = ChangeDecoding
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
Before: valueSnapshot(working, path, r.secrets),
|
|
After: valueSnapshotWithLookup(decodedInput, path, r.secrets, r.immutableLookup),
|
|
Origin: origin,
|
|
Reasons: []string{"applied retained defaults and canonical typed decoding"},
|
|
})
|
|
}
|
|
|
|
working = document.Clone(decodedInput)
|
|
if r.preCLI != nil {
|
|
for _, application := range r.postDecodeApps {
|
|
operationPath, relevant := applicationOperationPath(application, path, r.immutableLookup)
|
|
var before ValueSnapshot
|
|
if relevant {
|
|
before = valueSnapshot(working, path, r.secrets)
|
|
}
|
|
applyHistoryApplication(&working, application, replayIndex)
|
|
if !relevant {
|
|
continue
|
|
}
|
|
after := valueSnapshot(working, path, r.secrets)
|
|
if !before.Present && !after.Present {
|
|
continue
|
|
}
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
Before: before,
|
|
After: after,
|
|
Origin: historyApplicationOrigin(application, operationPath, r.immutableLookup),
|
|
})
|
|
}
|
|
replayed, _ = documentAtPath(working, path)
|
|
effectiveInput, _ := documentAtPathWithLookup(r.input, path, r.immutableLookup)
|
|
if !configurationValuesEqual(replayed, effectiveInput) {
|
|
origin := cloneOrigin(r.inputOrigins[path.String()])
|
|
origin.Operation = ChangeDecoding
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
Before: valueSnapshot(working, path, r.secrets),
|
|
After: valueSnapshotWithLookup(r.input, path, r.secrets, r.immutableLookup),
|
|
Origin: origin,
|
|
Reasons: []string{"applied the typed CLI assignment"},
|
|
})
|
|
}
|
|
working = document.Clone(r.input)
|
|
}
|
|
for _, snapshot := range r.resolutionReplay {
|
|
relevant := pathsIntersect(snapshot.change.Path, path)
|
|
var before ValueSnapshot
|
|
if relevant {
|
|
before = valueSnapshot(working, path, r.secrets)
|
|
}
|
|
replaceHistoryPath(&working, snapshot.change.Path, snapshot.after)
|
|
if !relevant {
|
|
continue
|
|
}
|
|
after := valueSnapshot(working, path, r.secrets)
|
|
if !before.Present && !after.Present {
|
|
continue
|
|
}
|
|
explanation.History = append(explanation.History, ValueChange{
|
|
Before: before,
|
|
After: after,
|
|
Origin: cloneOrigin(snapshot.change.Origin),
|
|
Reasons: append([]string(nil), snapshot.change.Reasons...),
|
|
Dependencies: clonePaths(snapshot.change.Dependencies),
|
|
})
|
|
}
|
|
|
|
if !explanation.Effective.Present && len(explanation.History) == 0 {
|
|
return Explanation{}, false
|
|
}
|
|
if name, ok := r.secretReferences[path.String()]; ok && !pathIsSecret(r.secrets, path) {
|
|
explanation.SecretReference = &SecretReferenceInfo{Name: name}
|
|
}
|
|
return explanation, true
|
|
}
|