124 lines
3.9 KiB
Go
124 lines
3.9 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
|
|
rendered.WriteString("path: ")
|
|
rendered.WriteString(strconv.Quote(explanation.Path.String()))
|
|
rendered.WriteByte('\n')
|
|
writeExplanationOrigin(&rendered, "effective source", explanation.Origin, r.sources)
|
|
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.Layer == LayerBase || change.Origin.Layer == LayerProfile || change.Origin.Layer == LayerCLI {
|
|
rendered.WriteString("policy-validated input\n")
|
|
} else {
|
|
rendered.WriteString("canonical typed\n")
|
|
}
|
|
writeExplanationOrigin(&rendered, " source", change.Origin, r.sources)
|
|
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, sources []SourceInfo) {
|
|
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 := sourceLabel(sources, 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')
|
|
}
|
|
}
|
|
|
|
func sourceLabel(sources []SourceInfo, sourceID string) string {
|
|
for _, source := range sources {
|
|
if source.ID == sourceID {
|
|
return source.Label
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
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')
|
|
}
|
|
}
|
|
}
|