Add configuration path discovery
This commit is contained in:
@@ -3,6 +3,7 @@ package ckconfig
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
commandkit "go.maximumdirect.net/commandkit"
|
||||
@@ -16,8 +17,37 @@ type explanationReport struct {
|
||||
Origin explanationOriginDTO `json:"origin" yaml:"origin"`
|
||||
History []explanationChangeDTO `json:"history" yaml:"history"`
|
||||
SecretReference *secretReferenceDTO `json:"secret_reference" yaml:"secret_reference"`
|
||||
Metadata *explanationMetadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type explanationMetadata struct {
|
||||
Explicit *bool `json:"explicit,omitempty"`
|
||||
Null *bool `json:"null,omitempty"`
|
||||
History []metadataHistoryRecord `json:"history,omitempty"`
|
||||
}
|
||||
|
||||
type metadataHistoryRecord struct {
|
||||
Path string `json:"path"`
|
||||
Previous []explanationOriginDTO `json:"previous,omitempty"`
|
||||
}
|
||||
|
||||
type pathDiscoveryReport struct {
|
||||
Scope string `json:"scope"`
|
||||
Paths []pathDiscoveryEntry `json:"paths"`
|
||||
}
|
||||
|
||||
type pathDiscoveryEntry struct {
|
||||
Path string `json:"path"`
|
||||
Present bool `json:"present"`
|
||||
Metadata *pathMetadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type pathMetadata struct {
|
||||
Explicit bool `json:"explicit"`
|
||||
}
|
||||
|
||||
var pathDiscoveryTemplate = template.Must(commandkit.ParseTextTemplate("paths", "{{range .Paths}}{{printf \"%q\" .Path}} {{if .Present}}{{if .Metadata.Explicit}}explicit{{else}}non-explicit{{end}}{{else}}historical{{end}}\n{{end}}", nil))
|
||||
|
||||
type snapshotDTO struct {
|
||||
Present bool `json:"present" yaml:"present"`
|
||||
Redacted bool `json:"redacted" yaml:"redacted"`
|
||||
@@ -72,6 +102,31 @@ func runConfigExplanation[I, S any](invocation commandkit.Invocation, plan *conf
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return configFailure("Configuration explanation was canceled.", acquired.Explanation, err)
|
||||
}
|
||||
if plan.list {
|
||||
if acquired.Value.Metadata == nil {
|
||||
return configFailure("Configuration scope did not provide path metadata.", acquired.Explanation, fmt.Errorf("configuration scope %q declared path discovery without metadata", name))
|
||||
}
|
||||
report := pathDiscoveryReport{Scope: name, Paths: []pathDiscoveryEntry{}}
|
||||
for _, path := range acquired.Value.Metadata.HistoricalPaths() {
|
||||
if plan.under != nil && !pathWithin(path, *plan.under) {
|
||||
continue
|
||||
}
|
||||
info, present := acquired.Value.Metadata.Lookup(path)
|
||||
if !present && (!plan.historical || plan.explicit.Set) {
|
||||
continue
|
||||
}
|
||||
if present && plan.explicit.Set && info.Explicit != plan.explicit.Value {
|
||||
continue
|
||||
}
|
||||
entry := pathDiscoveryEntry{Path: path.String(), Present: present}
|
||||
if present {
|
||||
entry.Metadata = &pathMetadata{Explicit: info.Explicit}
|
||||
}
|
||||
report.Paths = append(report.Paths, entry)
|
||||
}
|
||||
_ = reporter.Report(commandkit.Report{Value: report, TextTemplate: pathDiscoveryTemplate})
|
||||
return commandkit.Success()
|
||||
}
|
||||
if acquired.Value.Explanation == nil {
|
||||
cause := fmt.Errorf("configuration scope %q did not provide an explanation reader", name)
|
||||
return configFailure("Configuration scope did not provide explanation data.", acquired.Explanation, cause)
|
||||
@@ -85,20 +140,59 @@ func runConfigExplanation[I, S any](invocation commandkit.Invocation, plan *conf
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return configFailure("Configuration explanation was canceled.", acquired.Explanation, err)
|
||||
}
|
||||
report := makeExplanationReport(name, explanation)
|
||||
if acquired.Value.Metadata != nil {
|
||||
metadata := &explanationMetadata{}
|
||||
if info, ok := acquired.Value.Metadata.Lookup(plan.path); ok {
|
||||
metadata.Explicit, metadata.Null = &info.Explicit, &info.Null
|
||||
}
|
||||
for _, change := range acquired.Value.Metadata.History(plan.path) {
|
||||
entry := metadataHistoryRecord{Path: change.Path.String()}
|
||||
for _, previous := range change.Previous {
|
||||
entry.Previous = append(entry.Previous, originReport(previous))
|
||||
}
|
||||
metadata.History = append(metadata.History, entry)
|
||||
}
|
||||
if metadata.Explicit != nil || len(metadata.History) > 0 {
|
||||
report.Metadata = metadata
|
||||
}
|
||||
}
|
||||
text := template.Must(commandkit.ParseTextTemplate("explanation", "{{render .}}", template.FuncMap{
|
||||
"render": func(explanationReport) (string, error) {
|
||||
"render": func(report explanationReport) (string, error) {
|
||||
var output bytes.Buffer
|
||||
err := acquired.Value.Explanation.WriteExplanation(&output, plan.path)
|
||||
if err == nil && report.Metadata != nil && report.Metadata.Explicit != nil {
|
||||
_, err = fmt.Fprintf(&output, "Explicit: %t\nNull: %t\n", *report.Metadata.Explicit, *report.Metadata.Null)
|
||||
}
|
||||
return output.String(), err
|
||||
},
|
||||
}))
|
||||
renderErr := reporter.Report(commandkit.Report{Value: makeExplanationReport(name, explanation), TextTemplate: text})
|
||||
renderErr := reporter.Report(commandkit.Report{Value: report, TextTemplate: text})
|
||||
if renderErr != nil && invocation.Context.Err() != nil {
|
||||
return configFailure("Configuration explanation was canceled.", acquired.Explanation, invocation.Context.Err())
|
||||
}
|
||||
return commandkit.Success()
|
||||
}
|
||||
|
||||
func pathWithin(path, parent configkit.Path) bool {
|
||||
tokens := func(value string) []string {
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(strings.TrimPrefix(value, "/"), "/")
|
||||
}
|
||||
child, prefix := tokens(path.String()), tokens(parent.String())
|
||||
if len(prefix) > len(child) {
|
||||
return false
|
||||
}
|
||||
for index := range prefix {
|
||||
if prefix[index] != child[index] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func makeExplanationReport(scope string, explanation configkit.Explanation) explanationReport {
|
||||
report := explanationReport{
|
||||
Scope: scope,
|
||||
|
||||
Reference in New Issue
Block a user