Port native configuration reports
This commit is contained in:
@@ -6,41 +6,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
commandkit "gitea.maximumdirect.net/eric/CommandKit"
|
||||
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
||||
)
|
||||
|
||||
func newConfigExplainCommand[C any](prepared *preparedConfigSuite[C]) (Command, error) {
|
||||
flags := configInputFlags(prepared.suite, "text")
|
||||
flags = append(flags, ValueFlag[configInput[C]](FlagMeta{
|
||||
Name: "path",
|
||||
Usage: "Configuration path as a JSON Pointer; an empty value selects the root.",
|
||||
Required: true,
|
||||
}, nil, parseString, func(input *configInput[C], value Supplied[string]) {
|
||||
input.Path = value
|
||||
}))
|
||||
spec := CommandSpec[configInput[C]]{
|
||||
Name: string(ConfigExplain),
|
||||
Summary: "Explain the effective and historical value at a configuration path.",
|
||||
Flags: flags,
|
||||
CheckInput: func(input configInput[C]) error {
|
||||
if _, err := preflightConfig(prepared, input, ConfigExplain); err != nil {
|
||||
return err
|
||||
}
|
||||
if !input.Path.Set {
|
||||
return fmt.Errorf("--path is required")
|
||||
}
|
||||
if _, err := configkit.ParsePath(input.Path.Value); err != nil {
|
||||
return fmt.Errorf("invalid configuration path %q: %w", input.Path.Value, err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
||||
return runConfigExplanation(invocation, prepared, input)
|
||||
},
|
||||
}
|
||||
return NewCommand(spec)
|
||||
}
|
||||
|
||||
type explanationReport struct {
|
||||
Scope string `yaml:"scope"`
|
||||
Path string `yaml:"path"`
|
||||
@@ -82,70 +51,56 @@ type secretReferenceDTO struct {
|
||||
Name string `yaml:"name"`
|
||||
}
|
||||
|
||||
func runConfigExplanation[C any](invocation Invocation, prepared *preparedConfigSuite[C], input configInput[C]) Outcome {
|
||||
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
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return Failure(err)
|
||||
return configOperationFailure(err, nil, false)
|
||||
}
|
||||
scopeNames, err := selectConfigScopes(prepared, input, ConfigExplain)
|
||||
if err != nil {
|
||||
return Failure(err)
|
||||
if selected.scope.scope.Acquire == nil {
|
||||
return configOperationFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), nil, false)
|
||||
}
|
||||
if !input.Path.Set {
|
||||
return Failure(fmt.Errorf("--path is required"))
|
||||
}
|
||||
path, err := configkit.ParsePath(input.Path.Value)
|
||||
if err != nil {
|
||||
return Usage(fmt.Errorf("invalid configuration path %q: %w", input.Path.Value, err))
|
||||
}
|
||||
name := scopeNames[0]
|
||||
contexts := contextsForScopes(prepared, input, scopeNames)
|
||||
scope, ok := findConfigScope(prepared, name)
|
||||
if !ok || scope.Acquire == nil {
|
||||
return Failure(fmt.Errorf("configuration scope %q has no acquisition callback", name))
|
||||
}
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return explanationFailure(err, nil, false)
|
||||
}
|
||||
acquired, acquireErr := scope.Acquire(invocation.Context, ConfigRequest{Operation: ConfigExplain, Selection: input.Selection}, contexts[name])
|
||||
diagnostics := append([]Diagnostic(nil), acquired.Diagnostics...)
|
||||
acquired, acquireErr := selected.scope.scope.Acquire(invocation.Context, Request{
|
||||
Operation: Explain,
|
||||
Selection: plan.selection,
|
||||
}, selected.input, state)
|
||||
diagnostics := append([]commandkit.Diagnostic(nil), acquired.Diagnostics...)
|
||||
if acquireErr != nil {
|
||||
return explanationFailure(acquireErr, diagnostics, false)
|
||||
return configOperationFailure(acquireErr, diagnostics, false)
|
||||
}
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return explanationFailure(err, diagnostics, false)
|
||||
return configOperationFailure(err, diagnostics, false)
|
||||
}
|
||||
if acquired.Value.Explanation == nil {
|
||||
return configOperationFailure(fmt.Errorf("configuration scope %q did not provide an explanation reader", name), diagnostics, false)
|
||||
}
|
||||
|
||||
if acquired.Value.Explanation == nil {
|
||||
return explanationFailure(fmt.Errorf("configuration scope %q did not provide an explanation reader", name), diagnostics, false)
|
||||
}
|
||||
explanation, found := acquired.Value.Explanation.Explain(path)
|
||||
explanation, found := acquired.Value.Explanation.Explain(plan.path)
|
||||
if !found {
|
||||
return explanationFailure(fmt.Errorf("configuration path %q is not known in scope %q", path.String(), name), diagnostics, false)
|
||||
return configOperationFailure(fmt.Errorf("configuration path %q is not known in scope %q", plan.path.String(), name), diagnostics, false)
|
||||
}
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return explanationFailure(err, diagnostics, false)
|
||||
return configOperationFailure(err, diagnostics, false)
|
||||
}
|
||||
if input.Format != "yaml" {
|
||||
return writeNativeExplanation(invocation.Streams.Out, acquired.Value.Explanation, path, diagnostics)
|
||||
if plan.format != "yaml" {
|
||||
return writeNativeExplanation(invocation.Streams.Out, acquired.Value.Explanation, plan.path, diagnostics)
|
||||
}
|
||||
var report bytes.Buffer
|
||||
if err := encodeYAML(&report, makeExplanationReport(name, explanation)); err != nil {
|
||||
return explanationFailure(err, diagnostics, false)
|
||||
return configOperationFailure(err, diagnostics, false)
|
||||
}
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return explanationFailure(err, diagnostics, false)
|
||||
return configOperationFailure(err, diagnostics, false)
|
||||
}
|
||||
if err := writeReport(invocation.Streams.Out, report.Bytes()); err != nil {
|
||||
outcome := explanationFailure(err, diagnostics, true)
|
||||
outcome.DeliveryError = err
|
||||
return outcome
|
||||
if err := writeConfigReport(invocation.Streams.Out, report.Bytes()); err != nil {
|
||||
return configDeliveryFailure(err, diagnostics, true)
|
||||
}
|
||||
outcome := Success()
|
||||
outcome := commandkit.Success()
|
||||
outcome.Diagnostics = diagnostics
|
||||
return outcome
|
||||
}
|
||||
|
||||
func writeNativeExplanation(writer io.Writer, reader ExplanationReader, path configkit.Path, diagnostics []Diagnostic) Outcome {
|
||||
func writeNativeExplanation(writer io.Writer, reader ExplanationReader, path configkit.Path, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
|
||||
checked := &checkedWriter{writer: writer}
|
||||
renderErr := reader.WriteExplanation(checked, path)
|
||||
if checked.failure != nil {
|
||||
@@ -156,11 +111,11 @@ func writeNativeExplanation(writer io.Writer, reader ExplanationReader, path con
|
||||
}
|
||||
}
|
||||
if renderErr != nil {
|
||||
outcome := explanationFailure(renderErr, diagnostics, true)
|
||||
outcome := configOperationFailure(renderErr, diagnostics, true)
|
||||
outcome.DeliveryError = checked.failure
|
||||
return outcome
|
||||
}
|
||||
outcome := Success()
|
||||
outcome := commandkit.Success()
|
||||
outcome.Diagnostics = diagnostics
|
||||
return outcome
|
||||
}
|
||||
@@ -213,18 +168,3 @@ func originReport(origin configkit.Origin) explanationOriginDTO {
|
||||
}
|
||||
return report
|
||||
}
|
||||
|
||||
func locationReports(locations []configkit.Location) []locationDTO {
|
||||
report := make([]locationDTO, 0, len(locations))
|
||||
for _, location := range locations {
|
||||
report = append(report, locationDTO{SourceID: location.SourceID, Line: location.Line, Column: location.Column})
|
||||
}
|
||||
return report
|
||||
}
|
||||
|
||||
func explanationFailure(cause error, diagnostics []Diagnostic, reportStarted bool) Outcome {
|
||||
outcome := Failure(cause)
|
||||
outcome.Diagnostics = diagnostics
|
||||
outcome.ReportIncomplete = reportStarted
|
||||
return outcome
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user