130 lines
4.4 KiB
Go
130 lines
4.4 KiB
Go
package engine
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
errExplanationUnavailable = errors.New("configkit: configuration explanation is unavailable")
|
|
errExplanationUnknownPath = errors.New("configkit: configuration explanation path is unknown")
|
|
)
|
|
|
|
// WriteExplanation writes a deterministic human-readable rendering of Explain.
|
|
// Unknown paths produce no output. Writer failures may leave partial output.
|
|
func (r *Inspection) WriteExplanation(output io.Writer, path Path) error {
|
|
if r == nil || output == nil {
|
|
return errExplanationUnavailable
|
|
}
|
|
explanation, ok := r.Explain(path)
|
|
if !ok {
|
|
return errExplanationUnknownPath
|
|
}
|
|
var rendered strings.Builder
|
|
labels := make(map[string]string, len(r.sources))
|
|
for _, source := range r.sources {
|
|
labels[source.ID] = source.Label
|
|
}
|
|
rendered.WriteString("path: ")
|
|
rendered.WriteString(strconv.Quote(explanation.Path.String()))
|
|
rendered.WriteByte('\n')
|
|
writeExplanationOrigin(&rendered, "effective source", explanation.Origin, labels)
|
|
writeExplanationSnapshot(&rendered, "effective (canonical typed)", explanation.Effective, 0)
|
|
if explanation.SecretReference != nil {
|
|
rendered.WriteString("secret reference: ")
|
|
rendered.WriteString(strconv.Quote(explanation.SecretReference.Name))
|
|
rendered.WriteString(" (resolved from process environment)\n")
|
|
}
|
|
rendered.WriteString("history:\n")
|
|
for _, change := range explanation.History {
|
|
rendered.WriteString(" - representation: ")
|
|
if change.Origin.Operation == ChangeDecoding {
|
|
rendered.WriteString("input to canonical typed\n")
|
|
} else if change.Origin.Operation == ChangeInheritance {
|
|
rendered.WriteString("structural inheritance\n")
|
|
} 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")
|
|
}
|
|
writeExplanationOrigin(&rendered, " source", change.Origin, labels)
|
|
writeExplanationSnapshot(&rendered, " before", change.Before, 4)
|
|
writeExplanationSnapshot(&rendered, " after", change.After, 4)
|
|
for _, reason := range change.Reasons {
|
|
rendered.WriteString(" reason: ")
|
|
rendered.WriteString(strconv.Quote(reason))
|
|
rendered.WriteByte('\n')
|
|
}
|
|
for _, dependency := range change.Dependencies {
|
|
rendered.WriteString(" dependency: ")
|
|
rendered.WriteString(strconv.Quote(dependency.String()))
|
|
rendered.WriteByte('\n')
|
|
}
|
|
}
|
|
data := []byte(rendered.String())
|
|
written, err := output.Write(data)
|
|
if err == nil && written != len(data) {
|
|
return io.ErrShortWrite
|
|
}
|
|
return err
|
|
}
|
|
|
|
func writeExplanationOrigin(output *strings.Builder, label string, origin Origin, labels map[string]string) {
|
|
output.WriteString(label)
|
|
output.WriteString(": layer=")
|
|
output.WriteString(strconv.Quote(string(origin.Layer)))
|
|
if origin.Operation != "" {
|
|
output.WriteString(" operation=")
|
|
output.WriteString(strconv.Quote(string(origin.Operation)))
|
|
}
|
|
output.WriteByte('\n')
|
|
for _, location := range origin.Locations {
|
|
output.WriteString(" location: source=")
|
|
output.WriteString(strconv.Quote(location.SourceID))
|
|
if label := labels[location.SourceID]; label != "" {
|
|
output.WriteString(" label=")
|
|
output.WriteString(strconv.Quote(label))
|
|
}
|
|
output.WriteString(" line=")
|
|
output.WriteString(strconv.Itoa(location.Line))
|
|
output.WriteString(" column=")
|
|
output.WriteString(strconv.Itoa(location.Column))
|
|
output.WriteByte('\n')
|
|
}
|
|
for _, step := range origin.Inheritance {
|
|
output.WriteString(" inherited: from=")
|
|
output.WriteString(strconv.Quote(step.From.String()))
|
|
output.WriteString(" to=")
|
|
output.WriteString(strconv.Quote(step.To.String()))
|
|
output.WriteString(" reference=")
|
|
output.WriteString(strconv.Quote(step.Reference.String()))
|
|
output.WriteByte('\n')
|
|
}
|
|
}
|
|
|
|
func writeExplanationSnapshot(output *strings.Builder, label string, snapshot ValueSnapshot, indent int) {
|
|
if !snapshot.Present {
|
|
output.WriteString(label)
|
|
output.WriteString(": absent\n")
|
|
return
|
|
}
|
|
output.WriteString(label)
|
|
if snapshot.Redacted {
|
|
output.WriteString(" (redacted)")
|
|
}
|
|
output.WriteString(":\n")
|
|
prefix := strings.Repeat(" ", indent+2)
|
|
for _, line := range strings.SplitAfter(snapshot.YAML, "\n") {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
output.WriteString(prefix)
|
|
output.WriteString(line)
|
|
if !strings.HasSuffix(line, "\n") {
|
|
output.WriteByte('\n')
|
|
}
|
|
}
|
|
}
|