71 lines
2.0 KiB
Go
71 lines
2.0 KiB
Go
package configkit
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"slices"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
yamladapter "gitea.maximumdirect.net/eric/ConfigKit/internal/yaml"
|
|
)
|
|
|
|
var errEffectiveUnavailable = errors.New("configkit: effective configuration is unavailable")
|
|
|
|
// WriteEffective writes deterministic redacted YAML from the frozen resolved
|
|
// snapshot. The output is intended for inspection and is not promised reloadable.
|
|
// Writer errors, including short writes, are returned and may leave partial output.
|
|
func (r *Result[T]) WriteEffective(output io.Writer) error {
|
|
if r == nil || r.final == nil || output == nil {
|
|
return errEffectiveUnavailable
|
|
}
|
|
safe := redactedDocument(r.final, r.secrets, Path{})
|
|
return yamladapter.Write(output, safe)
|
|
}
|
|
|
|
func redactedDocument(node *document.Node, secret map[string]bool, path Path) *document.Node {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
if pathIsSecret(secret, path) {
|
|
return &document.Node{Kind: document.String, Text: "[REDACTED]"}
|
|
}
|
|
result := *node
|
|
switch node.Kind {
|
|
case document.Mapping:
|
|
result.Mapping = make([]document.Entry, len(node.Mapping))
|
|
copy(result.Mapping, node.Mapping)
|
|
slices.SortFunc(result.Mapping, func(left, right document.Entry) int {
|
|
return strings.Compare(left.Key, right.Key)
|
|
})
|
|
for index := range result.Mapping {
|
|
entry := &result.Mapping[index]
|
|
entry.Value = redactedDocument(entry.Value, secret, path.Key(entry.Key))
|
|
}
|
|
case document.Sequence:
|
|
result.Sequence = make([]*document.Node, len(node.Sequence))
|
|
for index, child := range node.Sequence {
|
|
result.Sequence[index] = redactedDocument(child, secret, path.Index(index))
|
|
}
|
|
}
|
|
return &result
|
|
}
|
|
|
|
func pathIsSecret(secret map[string]bool, path Path) bool {
|
|
if secret[""] {
|
|
return true
|
|
}
|
|
pointer := path.String()
|
|
for pointer != "" {
|
|
if secret[pointer] {
|
|
return true
|
|
}
|
|
position := strings.LastIndexByte(pointer, '/')
|
|
if position <= 0 {
|
|
break
|
|
}
|
|
pointer = pointer[:position]
|
|
}
|
|
return false
|
|
}
|