150 lines
5.9 KiB
Go
150 lines
5.9 KiB
Go
package ckconfig
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"text/template"
|
|
|
|
commandkit "go.maximumdirect.net/commandkit"
|
|
configkit "go.maximumdirect.net/configkit"
|
|
)
|
|
|
|
type explanationReport struct {
|
|
Scope string `json:"scope" yaml:"scope"`
|
|
Path string `json:"path" yaml:"path"`
|
|
Effective snapshotDTO `json:"effective" yaml:"effective"`
|
|
Origin explanationOriginDTO `json:"origin" yaml:"origin"`
|
|
History []explanationChangeDTO `json:"history" yaml:"history"`
|
|
SecretReference *secretReferenceDTO `json:"secret_reference" yaml:"secret_reference"`
|
|
}
|
|
|
|
type snapshotDTO struct {
|
|
Present bool `json:"present" yaml:"present"`
|
|
Redacted bool `json:"redacted" yaml:"redacted"`
|
|
YAML string `json:"yaml" yaml:"yaml"`
|
|
}
|
|
|
|
type explanationOriginDTO struct {
|
|
Layer string `json:"layer" yaml:"layer"`
|
|
Locations []locationDTO `json:"locations" yaml:"locations"`
|
|
Operation string `json:"operation" yaml:"operation"`
|
|
Inheritance []inheritanceStepDTO `json:"inheritance" yaml:"inheritance"`
|
|
}
|
|
|
|
type inheritanceStepDTO struct {
|
|
From string `json:"from" yaml:"from"`
|
|
To string `json:"to" yaml:"to"`
|
|
Reference string `json:"reference" yaml:"reference"`
|
|
Locations []locationDTO `json:"locations" yaml:"locations"`
|
|
}
|
|
|
|
type explanationChangeDTO struct {
|
|
Before snapshotDTO `json:"before" yaml:"before"`
|
|
After snapshotDTO `json:"after" yaml:"after"`
|
|
Origin explanationOriginDTO `json:"origin" yaml:"origin"`
|
|
Reasons []string `json:"reasons" yaml:"reasons"`
|
|
Dependencies []string `json:"dependencies" yaml:"dependencies"`
|
|
}
|
|
|
|
type secretReferenceDTO struct {
|
|
Name string `json:"name" yaml:"name"`
|
|
}
|
|
|
|
func runConfigExplanation[I, S any](invocation commandkit.Invocation, plan *configOperationPlan[I, S], state S) commandkit.Outcome {
|
|
selected := plan.scopes[0]
|
|
name := selected.scope.scope.Name
|
|
reporter := invocation.Reporter()
|
|
if err := invocation.Context.Err(); err != nil {
|
|
return configFailure("Configuration explanation was canceled.", commandkit.Explanation{}, err)
|
|
}
|
|
if selected.scope.scope.Acquire == nil {
|
|
cause := fmt.Errorf("configuration scope %q has no acquisition callback", name)
|
|
return configFailure(fmt.Sprintf("Unable to explain configuration scope %q.", name), commandkit.Explanation{}, cause)
|
|
}
|
|
acquired, acquireErr := selected.scope.scope.Acquire(invocation.Context, Request{
|
|
Operation: Explain,
|
|
Selection: plan.selection,
|
|
}, selected.input, state)
|
|
queueConfigDiagnostics(reporter, acquired.Diagnostics)
|
|
if acquireErr != nil {
|
|
return configFailure(fmt.Sprintf("Unable to explain configuration scope %q.", name), acquired.Explanation, acquireErr)
|
|
}
|
|
if err := invocation.Context.Err(); err != nil {
|
|
return configFailure("Configuration explanation was canceled.", acquired.Explanation, err)
|
|
}
|
|
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)
|
|
}
|
|
|
|
explanation, found := acquired.Value.Explanation.Explain(plan.path)
|
|
if !found {
|
|
cause := fmt.Errorf("configuration path %q is not known in scope %q", plan.path.String(), name)
|
|
return configFailure("The requested configuration path is not available.", acquired.Explanation, cause)
|
|
}
|
|
if err := invocation.Context.Err(); err != nil {
|
|
return configFailure("Configuration explanation was canceled.", acquired.Explanation, err)
|
|
}
|
|
text := template.Must(commandkit.ParseTextTemplate("explanation", "{{render .}}", template.FuncMap{
|
|
"render": func(explanationReport) (string, error) {
|
|
var output bytes.Buffer
|
|
err := acquired.Value.Explanation.WriteExplanation(&output, plan.path)
|
|
return output.String(), err
|
|
},
|
|
}))
|
|
renderErr := reporter.Report(commandkit.Report{Value: makeExplanationReport(name, explanation), TextTemplate: text})
|
|
if renderErr != nil && invocation.Context.Err() != nil {
|
|
return configFailure("Configuration explanation was canceled.", acquired.Explanation, invocation.Context.Err())
|
|
}
|
|
return commandkit.Success()
|
|
}
|
|
|
|
func makeExplanationReport(scope string, explanation configkit.Explanation) explanationReport {
|
|
report := explanationReport{
|
|
Scope: scope,
|
|
Path: explanation.Path.String(),
|
|
Effective: snapshotReport(explanation.Effective),
|
|
Origin: originReport(explanation.Origin),
|
|
History: make([]explanationChangeDTO, 0, len(explanation.History)),
|
|
}
|
|
for _, change := range explanation.History {
|
|
entry := explanationChangeDTO{
|
|
Before: snapshotReport(change.Before),
|
|
After: snapshotReport(change.After),
|
|
Origin: originReport(change.Origin),
|
|
Reasons: append([]string{}, change.Reasons...),
|
|
Dependencies: make([]string, 0, len(change.Dependencies)),
|
|
}
|
|
for _, dependency := range change.Dependencies {
|
|
entry.Dependencies = append(entry.Dependencies, dependency.String())
|
|
}
|
|
report.History = append(report.History, entry)
|
|
}
|
|
if explanation.SecretReference != nil {
|
|
report.SecretReference = &secretReferenceDTO{Name: explanation.SecretReference.Name}
|
|
}
|
|
return report
|
|
}
|
|
|
|
func snapshotReport(snapshot configkit.ValueSnapshot) snapshotDTO {
|
|
return snapshotDTO{Present: snapshot.Present, Redacted: snapshot.Redacted, YAML: snapshot.YAML}
|
|
}
|
|
|
|
func originReport(origin configkit.Origin) explanationOriginDTO {
|
|
report := explanationOriginDTO{
|
|
Layer: string(origin.Layer),
|
|
Locations: locationReports(origin.Locations),
|
|
Operation: string(origin.Operation),
|
|
Inheritance: make([]inheritanceStepDTO, 0, len(origin.Inheritance)),
|
|
}
|
|
for _, step := range origin.Inheritance {
|
|
report.Inheritance = append(report.Inheritance, inheritanceStepDTO{
|
|
From: step.From.String(),
|
|
To: step.To.String(),
|
|
Reference: step.Reference.String(),
|
|
Locations: locationReports(step.Locations),
|
|
})
|
|
}
|
|
return report
|
|
}
|