Render ConfigKit show and validate through reporter
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Invocation reporting implementation plan
|
||||
|
||||
Status: Stages 1–10 complete. Stage 11 is next.
|
||||
Status: Stages 1–11 complete. Stage 12 is next.
|
||||
|
||||
## Objective and authority
|
||||
|
||||
@@ -418,7 +418,7 @@ migration; Stage 20 will validate the assembled modules.
|
||||
|
||||
## Stage 11: Migrate configuration show and validate
|
||||
|
||||
Status: pending.
|
||||
Status: complete.
|
||||
|
||||
Primary files: [show.go](../../integrations/ckconfig/show.go),
|
||||
[validate.go](../../integrations/ckconfig/validate.go), and
|
||||
@@ -449,6 +449,15 @@ deferred context ordering, rendering failures, and report write failures.
|
||||
Update tests of superseded outcome fields to observable reports and
|
||||
`Presentation`, rather than dropping their original failure protection.
|
||||
|
||||
Configuration show and validate now submit provider messages through the shared
|
||||
reporter and render results through managed output. Tests retain acquisition
|
||||
ordering, shared state, YAML framing, cancellation with partial validation
|
||||
output, and cover warning visibility, terminal context, quiet rendering, and
|
||||
destination failures through `Presentation`. The ckconfig tests remain
|
||||
unrunnable because `sources.go`, `explain.go`, `diff.go`, and family wiring and
|
||||
tests still reference APIs scheduled for later migrations; Stage 20 will run the
|
||||
assembled suites.
|
||||
|
||||
## Stage 12: Migrate configuration sources, explain, and diff
|
||||
|
||||
Status: pending.
|
||||
|
||||
@@ -161,7 +161,7 @@ func TestValidationPreparesIndependentScopeInputsAndOneSharedState(t *testing.T)
|
||||
}
|
||||
app := newConfigTestApp(t, Command(suite))
|
||||
var output, diagnostic bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), []string{"config", "validate", "--scope", "all", "--config", "chosen.yaml", "--campaign", "spring"}, commandkit.Streams{Out: &output, Err: &diagnostic})
|
||||
outcome := app.Execute(context.Background(), []string{"config", "validate", "--scope", "all", "--config", "chosen.yaml", "--campaign", "spring", "--verbose"}, commandkit.Streams{Out: &output, Err: &diagnostic})
|
||||
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(diagnostic.String(), "app warning") || !strings.Contains(diagnostic.String(), "promptkit warning") {
|
||||
t.Fatalf("validation outcome=%#v diagnostics=%q", outcome, diagnostic.String())
|
||||
}
|
||||
@@ -289,7 +289,7 @@ func TestShowAcquiresAllViewsBeforeRenderingAndFramesNativeYAML(t *testing.T) {
|
||||
makeScope("promptkit", "---\nname: promptkit\n", providerErr),
|
||||
}}))
|
||||
outcome := app.Execute(context.Background(), []string{"config", "show", "--scope", "all"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause != providerErr || output.Len() != 0 {
|
||||
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause != providerErr || output.Len() != 0 || outcome.Presentation.Output.State != commandkit.DeliveryNone {
|
||||
t.Fatalf("aggregate provider failure outcome=%#v output=%q", outcome, output.String())
|
||||
}
|
||||
if len(acquisitionOutputLengths) != 2 || acquisitionOutputLengths[0] != 0 || acquisitionOutputLengths[1] != 0 {
|
||||
@@ -303,7 +303,7 @@ func TestShowAcquiresAllViewsBeforeRenderingAndFramesNativeYAML(t *testing.T) {
|
||||
makeScope("promptkit", "name: promptkit\n", nil),
|
||||
}}))
|
||||
outcome = app.Execute(context.Background(), []string{"config", "show", "--scope", "all"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeSuccess {
|
||||
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Presentation.Output.State != commandkit.DeliveryComplete {
|
||||
t.Fatalf("aggregate show outcome = %#v", outcome)
|
||||
}
|
||||
want := "---\n# scope: app\nname: app\n---\n# scope: promptkit\nname: promptkit\n"
|
||||
@@ -312,6 +312,109 @@ func TestShowAcquiresAllViewsBeforeRenderingAndFramesNativeYAML(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShowSeparatesWarningsFromTerminalContextAcrossModes(t *testing.T) {
|
||||
providerErr := errors.New("private provider cause")
|
||||
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{
|
||||
Name: "app", Operations: []Operation{Show}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
||||
return commandkit.Acquired[View]{
|
||||
Diagnostics: []commandkit.Diagnostic{commandkit.MessageDiagnostic(commandkit.SeverityWarning, "independent warning")},
|
||||
Explanation: commandkit.Explanation{Details: []commandkit.Detail{func(writer io.Writer) error {
|
||||
_, err := io.WriteString(writer, "native terminal context\n")
|
||||
return err
|
||||
}}},
|
||||
}, providerErr
|
||||
},
|
||||
}}}))
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
flags []string
|
||||
wantWarning bool
|
||||
wantTerminal bool
|
||||
}{
|
||||
{name: "ordinary", wantTerminal: true},
|
||||
{name: "verbose", flags: []string{"--verbose"}, wantWarning: true, wantTerminal: true},
|
||||
{name: "quiet", flags: []string{"--quiet"}, wantTerminal: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
args := append([]string{"config", "show"}, test.flags...)
|
||||
var output, diagnostic bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: &diagnostic})
|
||||
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause != providerErr {
|
||||
t.Fatalf("provider failure outcome = %#v", outcome)
|
||||
}
|
||||
if got := strings.Contains(diagnostic.String(), "independent warning"); got != test.wantWarning {
|
||||
t.Fatalf("warning visibility = %v, output %q", got, diagnostic.String())
|
||||
}
|
||||
if got := strings.Contains(diagnostic.String(), "native terminal context"); got != test.wantTerminal {
|
||||
t.Fatalf("terminal context visibility = %v, output %q", got, diagnostic.String())
|
||||
}
|
||||
if strings.Contains(diagnostic.String(), providerErr.Error()) {
|
||||
t.Fatalf("cause text was duplicated into presentation: %q", diagnostic.String())
|
||||
}
|
||||
if test.wantWarning && strings.Index(diagnostic.String(), "independent warning") > strings.Index(diagnostic.String(), "native terminal context") {
|
||||
t.Fatalf("deferred warning did not precede terminal context: %q", diagnostic.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShowAndValidationUseManagedRendering(t *testing.T) {
|
||||
t.Run("quiet show still records native render errors", func(t *testing.T) {
|
||||
renderErr := errors.New("native renderer failed")
|
||||
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{
|
||||
Name: "app", Operations: []Operation{Show}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
||||
return commandkit.Acquired[View]{Value: View{Effective: failedEffective{err: renderErr}}}, nil
|
||||
},
|
||||
}}}))
|
||||
var output, diagnostic bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), []string{"config", "show", "--quiet"}, commandkit.Streams{Out: &output, Err: &diagnostic})
|
||||
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Presentation.Output.RenderError, renderErr) || outcome.Cause != nil || output.Len() != 0 {
|
||||
t.Fatalf("quiet render failure outcome=%#v output=%q", outcome, output.String())
|
||||
}
|
||||
})
|
||||
|
||||
for _, operation := range []Operation{Show, Validate} {
|
||||
t.Run(string(operation)+" write failure", func(t *testing.T) {
|
||||
writeErr := errors.New("report destination failed")
|
||||
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{
|
||||
Name: "app",
|
||||
Operations: []Operation{operation},
|
||||
Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
||||
return commandkit.Acquired[View]{Value: View{Effective: effectiveValue("value: ready\n")}}, nil
|
||||
},
|
||||
}}}))
|
||||
commandName := "show"
|
||||
if operation == Validate {
|
||||
commandName = "validate"
|
||||
}
|
||||
outcome := app.Execute(context.Background(), []string{"config", commandName}, commandkit.Streams{Out: failingOutput{err: writeErr}, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.Presentation.Output.WriteError, writeErr) || outcome.Cause != nil {
|
||||
t.Fatalf("report write failure outcome = %#v", outcome)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run(string(operation)+" quiet suppression", func(t *testing.T) {
|
||||
writeErr := errors.New("quiet destination should not be used")
|
||||
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{{
|
||||
Name: "app",
|
||||
Operations: []Operation{operation},
|
||||
Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
||||
return commandkit.Acquired[View]{Value: View{Effective: effectiveValue("value: ready\n")}}, nil
|
||||
},
|
||||
}}}))
|
||||
commandName := "show"
|
||||
if operation == Validate {
|
||||
commandName = "validate"
|
||||
}
|
||||
outcome := app.Execute(context.Background(), []string{"config", commandName, "--quiet"}, commandkit.Streams{Out: failingOutput{err: writeErr}, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Presentation.Output.State != commandkit.DeliverySuppressed || outcome.Presentation.Output.WriteError != nil || outcome.Cause != nil {
|
||||
t.Fatalf("quiet report outcome = %#v", outcome)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourcesReadsNativeInventoryOnceAndPreservesZeroValues(t *testing.T) {
|
||||
reader := &sourceInventory{values: []configkit.SourceInfo{{
|
||||
ID: "synthetic", Label: "", Path: "", Layer: configkit.Layer("base"), Kind: configkit.SourceKind("mapping"), UnderlayIndex: 0,
|
||||
@@ -394,7 +497,7 @@ func TestReportWriteFailureTracksIncompleteDeliveryAndCancellation(t *testing.T)
|
||||
}}))
|
||||
var output bytes.Buffer
|
||||
outcome = app.Execute(ctx, []string{"config", "validate", "--scope", "all"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if !errors.Is(outcome.Cause, context.Canceled) || !outcome.ReportIncomplete || calls != 2 || output.String() != "app: valid\n" {
|
||||
if !errors.Is(outcome.Cause, context.Canceled) || outcome.Presentation.Output.State != commandkit.DeliveryComplete || calls != 2 || output.String() != "app: valid\n" {
|
||||
t.Fatalf("cancelled aggregate validation outcome=%#v calls=%d output=%q", outcome, calls, output.String())
|
||||
}
|
||||
}
|
||||
@@ -406,6 +509,12 @@ func (value effectiveValue) WriteEffective(writer io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
type failedEffective struct{ err error }
|
||||
|
||||
func (value failedEffective) WriteEffective(io.Writer) error {
|
||||
return value.err
|
||||
}
|
||||
|
||||
type sourceInventory struct {
|
||||
values []configkit.SourceInfo
|
||||
calls int
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ckconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
commandkit "gitea.maximumdirect.net/eric/CommandKit"
|
||||
@@ -9,10 +8,10 @@ import (
|
||||
|
||||
func runConfigOperation[I, S any](invocation commandkit.Invocation, operation Operation, input *configCommandInput[I, S]) commandkit.Outcome {
|
||||
if input == nil || input.plan == nil {
|
||||
return commandkit.Failure(errors.New("configuration operation was not prepared"))
|
||||
return commandkit.Failure("Configuration operation was not prepared.")
|
||||
}
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return commandkit.Failure(err)
|
||||
return configFailure("Configuration operation was canceled.", commandkit.Explanation{}, err)
|
||||
}
|
||||
plan := input.plan
|
||||
var state S
|
||||
@@ -31,19 +30,47 @@ func runConfigOperation[I, S any](invocation commandkit.Invocation, operation Op
|
||||
case Diff:
|
||||
return runConfigDiff(invocation, plan, state)
|
||||
default:
|
||||
return commandkit.Failure(fmt.Errorf("unsupported configuration operation %q", operation))
|
||||
return commandkit.Failure(fmt.Sprintf("Unsupported configuration operation %q.", operation))
|
||||
}
|
||||
}
|
||||
|
||||
func configOperationFailure(cause error, diagnostics []commandkit.Diagnostic, reportStarted bool) commandkit.Outcome {
|
||||
outcome := commandkit.Failure(cause)
|
||||
outcome.Diagnostics = diagnostics
|
||||
outcome.ReportIncomplete = reportStarted
|
||||
func queueConfigDiagnostics(reporter *commandkit.Reporter, diagnostics []commandkit.Diagnostic) {
|
||||
deferred := reporter.AtEnd()
|
||||
for _, diagnostic := range diagnostics {
|
||||
if diagnostic.Write == nil {
|
||||
continue
|
||||
}
|
||||
switch diagnostic.Severity {
|
||||
case commandkit.SeverityInfo:
|
||||
_ = deferred.Info("", diagnostic.Write)
|
||||
case commandkit.SeverityDebug:
|
||||
_ = deferred.Debug("", diagnostic.Write)
|
||||
case commandkit.SeverityProgress:
|
||||
_ = reporter.Progress("", diagnostic.Write)
|
||||
default:
|
||||
_ = deferred.Warn("", diagnostic.Write)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func configFailure(message string, explanation commandkit.Explanation, cause error) commandkit.Outcome {
|
||||
if explanation.Message != "" || len(explanation.Details) > 0 {
|
||||
message = explanation.Message
|
||||
}
|
||||
outcome := commandkit.Failure(message, explanation.Details...)
|
||||
if cause != nil {
|
||||
outcome = outcome.WithCause(cause)
|
||||
}
|
||||
return outcome
|
||||
}
|
||||
|
||||
func configDeliveryFailure(cause error, diagnostics []commandkit.Diagnostic, reportStarted bool) commandkit.Outcome {
|
||||
outcome := configOperationFailure(cause, diagnostics, reportStarted)
|
||||
outcome.DeliveryError = cause
|
||||
return outcome
|
||||
func configAcquisitionFailure(
|
||||
reporter *commandkit.Reporter,
|
||||
operation, scope string,
|
||||
acquired commandkit.Acquired[View],
|
||||
cause error,
|
||||
) commandkit.Outcome {
|
||||
queueConfigDiagnostics(reporter, acquired.Diagnostics)
|
||||
message := fmt.Sprintf("Unable to %s configuration scope %q.", operation, scope)
|
||||
return configFailure(message, acquired.Explanation, cause)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package ckconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
@@ -18,101 +17,76 @@ type effectiveDocument struct {
|
||||
|
||||
func runConfigShow[I, S any](invocation commandkit.Invocation, plan *configOperationPlan[I, S], state S) commandkit.Outcome {
|
||||
documents := make([]effectiveDocument, 0, len(plan.scopes))
|
||||
diagnostics := make([]commandkit.Diagnostic, 0)
|
||||
reporter := invocation.Reporter()
|
||||
for _, selected := range plan.scopes {
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return configOperationFailure(err, diagnostics, false)
|
||||
return configFailure("Configuration show was canceled.", commandkit.Explanation{}, err)
|
||||
}
|
||||
if selected.scope.scope.Acquire == nil {
|
||||
return configOperationFailure(fmt.Errorf("configuration scope %q has no acquisition callback", selected.scope.scope.Name), diagnostics, false)
|
||||
message := fmt.Sprintf("Configuration scope %q has no acquisition callback.", selected.scope.scope.Name)
|
||||
return commandkit.Failure(message)
|
||||
}
|
||||
acquired, acquireErr := selected.scope.scope.Acquire(invocation.Context, Request{
|
||||
Operation: Show,
|
||||
Selection: plan.selection,
|
||||
}, selected.input, state)
|
||||
diagnostics = append(diagnostics, acquired.Diagnostics...)
|
||||
if acquireErr != nil {
|
||||
return configOperationFailure(acquireErr, diagnostics, false)
|
||||
return configAcquisitionFailure(reporter, "show", selected.scope.scope.Name, acquired, acquireErr)
|
||||
}
|
||||
queueConfigDiagnostics(reporter, acquired.Diagnostics)
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return configOperationFailure(err, diagnostics, false)
|
||||
return configFailure("Configuration show was canceled.", acquired.Explanation, err)
|
||||
}
|
||||
if acquired.Value.Effective == nil {
|
||||
return configOperationFailure(fmt.Errorf("configuration scope %q did not provide an effective configuration writer", selected.scope.scope.Name), diagnostics, false)
|
||||
message := fmt.Sprintf("Configuration scope %q did not provide an effective configuration writer.", selected.scope.scope.Name)
|
||||
return commandkit.Failure(message)
|
||||
}
|
||||
documents = append(documents, effectiveDocument{name: selected.scope.scope.Name, view: acquired.Value})
|
||||
}
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return configOperationFailure(err, diagnostics, false)
|
||||
return configFailure("Configuration show was canceled.", commandkit.Explanation{}, err)
|
||||
}
|
||||
if len(documents) == 1 && plan.scopeName != "all" {
|
||||
checked := &checkedWriter{writer: invocation.Streams.Out}
|
||||
renderErr := documents[0].view.Effective.WriteEffective(checked)
|
||||
if checked.failure != nil {
|
||||
if renderErr == nil {
|
||||
renderErr = checked.failure
|
||||
} else {
|
||||
renderErr = errors.Join(renderErr, checked.failure)
|
||||
err := reporter.RenderOutput(func(writer io.Writer) error {
|
||||
return documents[0].view.Effective.WriteEffective(writer)
|
||||
})
|
||||
if err != nil && invocation.Context.Err() != nil {
|
||||
return configFailure("Configuration show was canceled.", commandkit.Explanation{}, invocation.Context.Err())
|
||||
}
|
||||
return commandkit.Success()
|
||||
}
|
||||
|
||||
err := reporter.RenderOutput(func(writer io.Writer) error {
|
||||
for index, document := range documents {
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
var native bytes.Buffer
|
||||
if err := document.view.Effective.WriteEffective(&native); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
framed, hasDirective := frameEffectiveYAML(document.name, native.Bytes())
|
||||
if index > 0 && hasDirective {
|
||||
if err := writeConfigReport(writer, []byte("...\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if index < len(documents)-1 && len(framed) > 0 && framed[len(framed)-1] != '\n' {
|
||||
framed = append(framed, '\n')
|
||||
}
|
||||
if err := writeConfigReport(writer, framed); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if renderErr != nil {
|
||||
outcome := configOperationFailure(renderErr, diagnostics, true)
|
||||
outcome.DeliveryError = checked.failure
|
||||
return outcome
|
||||
}
|
||||
outcome := commandkit.Success()
|
||||
outcome.Diagnostics = diagnostics
|
||||
return outcome
|
||||
return nil
|
||||
})
|
||||
if err != nil && invocation.Context.Err() != nil {
|
||||
return configFailure("Configuration show was canceled.", commandkit.Explanation{}, invocation.Context.Err())
|
||||
}
|
||||
|
||||
written := false
|
||||
for index, document := range documents {
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return configOperationFailure(err, diagnostics, written)
|
||||
}
|
||||
var native bytes.Buffer
|
||||
if err := document.view.Effective.WriteEffective(&native); err != nil {
|
||||
return configOperationFailure(err, diagnostics, written)
|
||||
}
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return configOperationFailure(err, diagnostics, written)
|
||||
}
|
||||
framed, hasDirective := frameEffectiveYAML(document.name, native.Bytes())
|
||||
if index > 0 && hasDirective {
|
||||
if err := writeConfigReport(invocation.Streams.Out, []byte("...\n")); err != nil {
|
||||
return configDeliveryFailure(err, diagnostics, true)
|
||||
}
|
||||
}
|
||||
if index < len(documents)-1 && len(framed) > 0 && framed[len(framed)-1] != '\n' {
|
||||
framed = append(framed, '\n')
|
||||
}
|
||||
if err := writeConfigReport(invocation.Streams.Out, framed); err != nil {
|
||||
return configDeliveryFailure(err, diagnostics, true)
|
||||
}
|
||||
written = true
|
||||
}
|
||||
outcome := commandkit.Success()
|
||||
outcome.Diagnostics = diagnostics
|
||||
return outcome
|
||||
}
|
||||
|
||||
type checkedWriter struct {
|
||||
writer io.Writer
|
||||
failure error
|
||||
}
|
||||
|
||||
func (writer *checkedWriter) Write(content []byte) (int, error) {
|
||||
if writer.failure != nil {
|
||||
return 0, writer.failure
|
||||
}
|
||||
written, err := writer.writer.Write(content)
|
||||
if err == nil && written != len(content) {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
if err != nil {
|
||||
writer.failure = err
|
||||
}
|
||||
return written, err
|
||||
return commandkit.Success()
|
||||
}
|
||||
|
||||
type yamlPreamble struct {
|
||||
|
||||
@@ -3,6 +3,7 @@ package ckconfig
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
commandkit "gitea.maximumdirect.net/eric/CommandKit"
|
||||
@@ -10,44 +11,49 @@ import (
|
||||
)
|
||||
|
||||
func runConfigValidation[I, S any](invocation commandkit.Invocation, plan *configOperationPlan[I, S], state S) commandkit.Outcome {
|
||||
diagnostics := make([]commandkit.Diagnostic, 0)
|
||||
reporter := invocation.Reporter()
|
||||
writtenRecord := false
|
||||
for _, selected := range plan.scopes {
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return configOperationFailure(err, diagnostics, writtenRecord)
|
||||
return configFailure("Configuration validation was canceled.", commandkit.Explanation{}, err)
|
||||
}
|
||||
if selected.scope.scope.Acquire == nil {
|
||||
return configOperationFailure(fmt.Errorf("configuration scope %q has no acquisition callback", selected.scope.scope.Name), diagnostics, writtenRecord)
|
||||
message := fmt.Sprintf("Configuration scope %q has no acquisition callback.", selected.scope.scope.Name)
|
||||
return commandkit.Failure(message)
|
||||
}
|
||||
acquired, acquireErr := selected.scope.scope.Acquire(invocation.Context, Request{
|
||||
Operation: Validate,
|
||||
Selection: plan.selection,
|
||||
}, selected.input, state)
|
||||
diagnostics = append(diagnostics, acquired.Diagnostics...)
|
||||
if acquireErr != nil {
|
||||
return configOperationFailure(acquireErr, diagnostics, writtenRecord)
|
||||
return configAcquisitionFailure(reporter, "validate", selected.scope.scope.Name, acquired, acquireErr)
|
||||
}
|
||||
queueConfigDiagnostics(reporter, acquired.Diagnostics)
|
||||
if err := invocation.Context.Err(); err != nil {
|
||||
return configOperationFailure(err, diagnostics, writtenRecord)
|
||||
return configFailure("Configuration validation was canceled.", acquired.Explanation, err)
|
||||
}
|
||||
|
||||
record, err := validationSuccessRecord(selected.scope.scope.Name, plan.format)
|
||||
if err != nil {
|
||||
return configOperationFailure(err, diagnostics, writtenRecord)
|
||||
}
|
||||
if plan.format == "yaml" && !writtenRecord {
|
||||
if err := writeConfigReport(invocation.Streams.Out, []byte("scopes:\n")); err != nil {
|
||||
return configDeliveryFailure(err, diagnostics, true)
|
||||
err := reporter.RenderOutput(func(writer io.Writer) error {
|
||||
record, err := validationSuccessRecord(selected.scope.scope.Name, plan.format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := writeConfigReport(invocation.Streams.Out, record); err != nil {
|
||||
return configDeliveryFailure(err, diagnostics, true)
|
||||
if plan.format == "yaml" && !writtenRecord {
|
||||
if err := writeConfigReport(writer, []byte("scopes:\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return writeConfigReport(writer, record)
|
||||
})
|
||||
if err != nil {
|
||||
if ctxErr := invocation.Context.Err(); ctxErr != nil {
|
||||
return configFailure("Configuration validation was canceled.", acquired.Explanation, ctxErr)
|
||||
}
|
||||
return commandkit.Success()
|
||||
}
|
||||
writtenRecord = true
|
||||
}
|
||||
outcome := commandkit.Success()
|
||||
outcome.Diagnostics = diagnostics
|
||||
return outcome
|
||||
return commandkit.Success()
|
||||
}
|
||||
|
||||
func validationSuccessRecord(scope, format string) ([]byte, error) {
|
||||
|
||||
Reference in New Issue
Block a user