2026-10-07 13:50:48 +00:00
|
|
|
package commandkit
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"go.yaml.in/yaml/v4"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func configOperationDeclared[C any](scopes []ConfigScope[C], operation ConfigOperation) bool {
|
|
|
|
|
for _, scope := range scopes {
|
|
|
|
|
if containsOperation(scope.Operations, operation) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newConfigValidateCommand[C any](suite ConfigSuite[C]) (Command, error) {
|
|
|
|
|
spec := CommandSpec[configInput[C]]{
|
|
|
|
|
Name: string(ConfigValidate),
|
|
|
|
|
Summary: "Validate selected configuration scopes.",
|
|
|
|
|
Flags: configInputFlags(suite, "text"),
|
|
|
|
|
CheckInput: func(input configInput[C]) error {
|
|
|
|
|
_, err := preflightConfig(suite, input, ConfigValidate)
|
|
|
|
|
return err
|
|
|
|
|
},
|
|
|
|
|
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
|
|
|
|
return runConfigValidation(invocation, suite, input)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return NewCommand(spec)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runConfigValidation[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return Failure(err)
|
|
|
|
|
}
|
2026-10-07 13:50:48 +00:00
|
|
|
scopeNames, err := selectConfigScopes(suite, input, ConfigValidate)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Failure(err)
|
|
|
|
|
}
|
|
|
|
|
contexts := contextsForScopes(suite, input, scopeNames)
|
|
|
|
|
diagnostics := make([]Diagnostic, 0)
|
|
|
|
|
writtenRecord := false
|
|
|
|
|
for _, name := range scopeNames {
|
|
|
|
|
scope, ok := findConfigScope(suite.Scopes, name)
|
|
|
|
|
if !ok || scope.Acquire == nil {
|
|
|
|
|
return validationFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, writtenRecord)
|
|
|
|
|
}
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return validationFailure(err, diagnostics, writtenRecord)
|
|
|
|
|
}
|
2026-10-07 13:50:48 +00:00
|
|
|
acquired, acquireErr := scope.Acquire(invocation.Context, ConfigRequest{Operation: ConfigValidate, Selection: input.Selection}, contexts[name])
|
|
|
|
|
diagnostics = append(diagnostics, acquired.Diagnostics...)
|
|
|
|
|
if acquireErr != nil {
|
|
|
|
|
return validationFailure(acquireErr, diagnostics, writtenRecord)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return validationFailure(err, diagnostics, writtenRecord)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 13:50:48 +00:00
|
|
|
record, err := validationSuccessRecord(name, input.Format)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return validationFailure(err, diagnostics, writtenRecord)
|
|
|
|
|
}
|
|
|
|
|
if input.Format == "yaml" && !writtenRecord {
|
|
|
|
|
if err := writeReport(invocation.Streams.Out, []byte("scopes:\n")); err != nil {
|
|
|
|
|
return validationFailure(err, diagnostics, true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err := writeReport(invocation.Streams.Out, record); err != nil {
|
|
|
|
|
return validationFailure(err, diagnostics, true)
|
|
|
|
|
}
|
|
|
|
|
writtenRecord = true
|
|
|
|
|
}
|
|
|
|
|
outcome := Success()
|
|
|
|
|
outcome.Diagnostics = diagnostics
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func validationFailure(cause error, diagnostics []Diagnostic, reportStarted bool) Outcome {
|
|
|
|
|
outcome := Failure(cause)
|
|
|
|
|
outcome.Diagnostics = diagnostics
|
|
|
|
|
outcome.ReportIncomplete = reportStarted
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func validationSuccessRecord(scope, format string) ([]byte, error) {
|
|
|
|
|
if format == "text" {
|
|
|
|
|
return []byte(scope + ": valid\n"), nil
|
|
|
|
|
}
|
|
|
|
|
encoded, err := yaml.Marshal(struct {
|
|
|
|
|
Name string `yaml:"name"`
|
|
|
|
|
Valid bool `yaml:"valid"`
|
|
|
|
|
}{Name: scope, Valid: true})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
lines := strings.Split(strings.TrimSuffix(string(encoded), "\n"), "\n")
|
|
|
|
|
var record bytes.Buffer
|
|
|
|
|
for index, line := range lines {
|
|
|
|
|
if index == 0 {
|
|
|
|
|
record.WriteString(" - ")
|
|
|
|
|
} else {
|
|
|
|
|
record.WriteString(" ")
|
|
|
|
|
}
|
|
|
|
|
record.WriteString(line)
|
|
|
|
|
record.WriteByte('\n')
|
|
|
|
|
}
|
|
|
|
|
return record.Bytes(), nil
|
|
|
|
|
}
|