2026-10-07 13:55:43 +00:00
|
|
|
package commandkit
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"strconv"
|
|
|
|
|
"unicode"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func newConfigShowCommand[C any](suite ConfigSuite[C]) (Command, error) {
|
|
|
|
|
spec := CommandSpec[configInput[C]]{
|
|
|
|
|
Name: string(ConfigShow),
|
|
|
|
|
Summary: "Show effective configuration as YAML.",
|
|
|
|
|
Flags: configInputFlags(suite, "yaml"),
|
|
|
|
|
CheckInput: func(input configInput[C]) error {
|
|
|
|
|
_, err := preflightConfig(suite, input, ConfigShow)
|
|
|
|
|
return err
|
|
|
|
|
},
|
|
|
|
|
Run: func(invocation Invocation, input configInput[C]) Outcome {
|
|
|
|
|
return runConfigShow(invocation, suite, input)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
return NewCommand(spec)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type effectiveDocument struct {
|
|
|
|
|
name string
|
|
|
|
|
view ConfigView
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runConfigShow[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:55:43 +00:00
|
|
|
scopeNames, err := selectConfigScopes(suite, input, ConfigShow)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Failure(err)
|
|
|
|
|
}
|
|
|
|
|
contexts := contextsForScopes(suite, input, scopeNames)
|
|
|
|
|
documents := make([]effectiveDocument, 0, len(scopeNames))
|
|
|
|
|
diagnostics := make([]Diagnostic, 0)
|
|
|
|
|
for _, name := range scopeNames {
|
|
|
|
|
scope, ok := findConfigScope(suite.Scopes, name)
|
|
|
|
|
if !ok || scope.Acquire == nil {
|
|
|
|
|
return showFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, false)
|
|
|
|
|
}
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return showFailure(err, diagnostics, false)
|
|
|
|
|
}
|
2026-10-07 13:55:43 +00:00
|
|
|
acquired, acquireErr := scope.Acquire(invocation.Context, ConfigRequest{Operation: ConfigShow, Selection: input.Selection}, contexts[name])
|
|
|
|
|
diagnostics = append(diagnostics, acquired.Diagnostics...)
|
|
|
|
|
if acquireErr != nil {
|
|
|
|
|
return showFailure(acquireErr, diagnostics, false)
|
|
|
|
|
}
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return showFailure(err, diagnostics, false)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 13:55:43 +00:00
|
|
|
if acquired.Value.Effective == nil {
|
|
|
|
|
return showFailure(fmt.Errorf("configuration scope %q did not provide an effective configuration writer", name), diagnostics, false)
|
|
|
|
|
}
|
|
|
|
|
documents = append(documents, effectiveDocument{name: name, view: acquired.Value})
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return showFailure(err, diagnostics, false)
|
|
|
|
|
}
|
2026-10-07 13:55:43 +00:00
|
|
|
if len(documents) == 1 && input.Scope != "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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if renderErr != nil {
|
|
|
|
|
outcome := showFailure(renderErr, diagnostics, true)
|
|
|
|
|
outcome.DeliveryError = checked.failure
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
outcome := Success()
|
|
|
|
|
outcome.Diagnostics = diagnostics
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
written := false
|
|
|
|
|
for index, document := range documents {
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return showFailure(err, diagnostics, written)
|
|
|
|
|
}
|
2026-10-07 13:55:43 +00:00
|
|
|
var native bytes.Buffer
|
|
|
|
|
if err := document.view.Effective.WriteEffective(&native); err != nil {
|
|
|
|
|
return showFailure(err, diagnostics, written)
|
|
|
|
|
}
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
|
|
|
|
return showFailure(err, diagnostics, written)
|
|
|
|
|
}
|
2026-10-09 01:30:17 +00:00
|
|
|
framed, hasDirective := frameEffectiveYAML(document.name, native.Bytes())
|
|
|
|
|
if index > 0 && hasDirective {
|
2026-10-07 13:55:43 +00:00
|
|
|
if err := writeReport(invocation.Streams.Out, []byte("...\n")); err != nil {
|
|
|
|
|
outcome := showFailure(err, diagnostics, true)
|
|
|
|
|
outcome.DeliveryError = err
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if index < len(documents)-1 && len(framed) > 0 && framed[len(framed)-1] != '\n' {
|
|
|
|
|
framed = append(framed, '\n')
|
|
|
|
|
}
|
|
|
|
|
if err := writeReport(invocation.Streams.Out, framed); err != nil {
|
|
|
|
|
outcome := showFailure(err, diagnostics, true)
|
|
|
|
|
outcome.DeliveryError = err
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
written = true
|
|
|
|
|
}
|
|
|
|
|
outcome := Success()
|
|
|
|
|
outcome.Diagnostics = diagnostics
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-09 01:30:17 +00:00
|
|
|
type yamlPreamble struct {
|
|
|
|
|
markerStart int
|
|
|
|
|
markerEnd int
|
|
|
|
|
markerFound bool
|
|
|
|
|
inlineMarker bool
|
|
|
|
|
preambleEnd int
|
|
|
|
|
hasDirective bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func analyzeYAMLPreamble(native []byte) yamlPreamble {
|
|
|
|
|
var analysis yamlPreamble
|
|
|
|
|
for start := 0; start < len(native); {
|
|
|
|
|
end := len(native)
|
|
|
|
|
if newline := bytes.IndexByte(native[start:], '\n'); newline >= 0 {
|
|
|
|
|
end = start + newline + 1
|
|
|
|
|
}
|
|
|
|
|
lineEnd := end
|
|
|
|
|
if lineEnd > start && native[lineEnd-1] == '\n' {
|
|
|
|
|
lineEnd--
|
|
|
|
|
}
|
|
|
|
|
trimmed := bytes.TrimSpace(native[start:lineEnd])
|
|
|
|
|
if isYAMLDocumentStart(trimmed) {
|
|
|
|
|
content := bytes.TrimSpace(trimmed[3:])
|
|
|
|
|
analysis.markerStart = start
|
|
|
|
|
analysis.markerEnd = end
|
|
|
|
|
analysis.markerFound = true
|
|
|
|
|
analysis.inlineMarker = len(content) > 0 && content[0] != '#'
|
|
|
|
|
return analysis
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
|
|
|
|
if bytes.HasPrefix(trimmed, []byte("%YAML ")) || bytes.HasPrefix(trimmed, []byte("%TAG ")) {
|
2026-10-09 01:30:17 +00:00
|
|
|
analysis.hasDirective = true
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
2026-10-09 01:30:17 +00:00
|
|
|
if len(trimmed) == 0 || trimmed[0] == '#' || bytes.HasPrefix(trimmed, []byte("%YAML ")) || bytes.HasPrefix(trimmed, []byte("%TAG ")) {
|
|
|
|
|
analysis.preambleEnd = end
|
|
|
|
|
} else {
|
|
|
|
|
return analysis
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
2026-10-09 01:30:17 +00:00
|
|
|
start = end
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
2026-10-09 01:30:17 +00:00
|
|
|
return analysis
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-09 01:30:17 +00:00
|
|
|
func frameEffectiveYAML(scope string, native []byte) ([]byte, bool) {
|
|
|
|
|
analysis := analyzeYAMLPreamble(native)
|
2026-10-07 13:55:43 +00:00
|
|
|
label := scope
|
|
|
|
|
for _, r := range scope {
|
|
|
|
|
if unicode.IsControl(r) {
|
|
|
|
|
label = strconv.Quote(scope)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
comment := []byte("# scope: " + label + "\n")
|
2026-10-09 01:30:17 +00:00
|
|
|
if analysis.markerFound {
|
|
|
|
|
out := make([]byte, 0, len(native)+len(comment))
|
|
|
|
|
// Inline content may be a block scalar; place the label before
|
|
|
|
|
// its marker rather than interrupting the native value.
|
|
|
|
|
if analysis.inlineMarker {
|
|
|
|
|
out = append(out, native[:analysis.markerStart]...)
|
2026-10-07 13:55:43 +00:00
|
|
|
out = append(out, comment...)
|
2026-10-09 01:30:17 +00:00
|
|
|
out = append(out, native[analysis.markerStart:]...)
|
|
|
|
|
return out, analysis.hasDirective
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
2026-10-09 01:30:17 +00:00
|
|
|
out = append(out, native[:analysis.markerEnd]...)
|
|
|
|
|
if analysis.markerEnd > analysis.markerStart && native[analysis.markerEnd-1] != '\n' {
|
|
|
|
|
out = append(out, '\n')
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
2026-10-09 01:30:17 +00:00
|
|
|
out = append(out, comment...)
|
|
|
|
|
out = append(out, native[analysis.markerEnd:]...)
|
|
|
|
|
return out, analysis.hasDirective
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
|
|
|
|
result := make([]byte, 0, len(native)+len(comment)+5)
|
2026-10-09 01:30:17 +00:00
|
|
|
if analysis.hasDirective {
|
|
|
|
|
result = append(result, native[:analysis.preambleEnd]...)
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
|
|
|
|
result = append(result, []byte("---\n")...)
|
|
|
|
|
result = append(result, comment...)
|
2026-10-09 01:30:17 +00:00
|
|
|
if analysis.hasDirective {
|
|
|
|
|
result = append(result, native[analysis.preambleEnd:]...)
|
2026-10-07 13:55:43 +00:00
|
|
|
} else {
|
|
|
|
|
result = append(result, native...)
|
|
|
|
|
}
|
2026-10-09 01:30:17 +00:00
|
|
|
return result, analysis.hasDirective
|
2026-10-07 13:55:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func showFailure(cause error, diagnostics []Diagnostic, reportStarted bool) Outcome {
|
|
|
|
|
outcome := Failure(cause)
|
|
|
|
|
outcome.Diagnostics = diagnostics
|
|
|
|
|
outcome.ReportIncomplete = reportStarted
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
2026-10-07 16:45:10 +00:00
|
|
|
|
2026-10-09 01:30:17 +00:00
|
|
|
func isYAMLDocumentStart(line []byte) bool {
|
|
|
|
|
return bytes.HasPrefix(line, []byte("---")) && (len(line) == 3 || line[3] == ' ' || line[3] == '\t')
|
2026-10-07 16:45:10 +00:00
|
|
|
}
|