Files

154 lines
5.6 KiB
Go
Raw Permalink Normal View History

package ckconfig
2026-10-07 14:17:46 +00:00
import (
"bytes"
"fmt"
"io"
2026-10-11 01:39:23 +00:00
commandkit "gitea.maximumdirect.net/eric/CommandKit"
2026-10-07 14:17:46 +00:00
configkit "gitea.maximumdirect.net/eric/ConfigKit"
)
type explanationReport struct {
Scope string `yaml:"scope"`
Path string `yaml:"path"`
Effective snapshotDTO `yaml:"effective"`
Origin explanationOriginDTO `yaml:"origin"`
History []explanationChangeDTO `yaml:"history"`
SecretReference *secretReferenceDTO `yaml:"secret_reference"`
}
type snapshotDTO struct {
Present bool `yaml:"present"`
Redacted bool `yaml:"redacted"`
YAML string `yaml:"yaml"`
}
type explanationOriginDTO struct {
Layer string `yaml:"layer"`
Locations []locationDTO `yaml:"locations"`
Operation string `yaml:"operation"`
Inheritance []inheritanceStepDTO `yaml:"inheritance"`
}
type inheritanceStepDTO struct {
From string `yaml:"from"`
To string `yaml:"to"`
Reference string `yaml:"reference"`
Locations []locationDTO `yaml:"locations"`
}
type explanationChangeDTO struct {
Before snapshotDTO `yaml:"before"`
After snapshotDTO `yaml:"after"`
Origin explanationOriginDTO `yaml:"origin"`
Reasons []string `yaml:"reasons"`
Dependencies []string `yaml:"dependencies"`
}
type secretReferenceDTO struct {
Name string `yaml:"name"`
}
2026-10-11 01:39:23 +00:00
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)
2026-10-07 14:17:46 +00:00
}
2026-10-11 01:39:23 +00:00
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)
2026-10-07 14:17:46 +00:00
}
2026-10-11 01:39:23 +00:00
acquired, acquireErr := selected.scope.scope.Acquire(invocation.Context, Request{
Operation: Explain,
Selection: plan.selection,
}, selected.input, state)
queueConfigDiagnostics(reporter, acquired.Diagnostics)
2026-10-07 14:17:46 +00:00
if acquireErr != nil {
return configFailure(fmt.Sprintf("Unable to explain configuration scope %q.", name), acquired.Explanation, acquireErr)
2026-10-07 14:17:46 +00:00
}
if err := invocation.Context.Err(); err != nil {
return configFailure("Configuration explanation was canceled.", acquired.Explanation, err)
}
2026-10-07 14:17:46 +00:00
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)
2026-10-07 14:17:46 +00:00
}
2026-10-11 01:39:23 +00:00
explanation, found := acquired.Value.Explanation.Explain(plan.path)
2026-10-07 14:17:46 +00:00
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)
2026-10-07 14:17:46 +00:00
}
if err := invocation.Context.Err(); err != nil {
return configFailure("Configuration explanation was canceled.", acquired.Explanation, err)
}
2026-10-11 01:39:23 +00:00
if plan.format != "yaml" {
err := reporter.RenderOutput(func(writer io.Writer) error {
return acquired.Value.Explanation.WriteExplanation(writer, plan.path)
})
if err != nil && invocation.Context.Err() != nil {
return configFailure("Configuration explanation was canceled.", acquired.Explanation, invocation.Context.Err())
2026-10-07 14:17:46 +00:00
}
return commandkit.Success()
2026-10-07 14:17:46 +00:00
}
return renderPreparedConfigReport(invocation, acquired.Explanation, func() ([]byte, error) {
var report bytes.Buffer
if err := encodeYAML(&report, makeExplanationReport(name, explanation)); err != nil {
return nil, err
}
return report.Bytes(), nil
})
2026-10-07 14:17:46 +00:00
}
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
}