2026-10-11 01:06:21 +00:00
|
|
|
package ckconfig
|
2026-10-07 14:05:26 +00:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
2026-10-11 01:39:23 +00:00
|
|
|
"io"
|
2026-10-07 14:05:26 +00:00
|
|
|
"strconv"
|
|
|
|
|
|
2026-10-11 01:39:23 +00:00
|
|
|
commandkit "gitea.maximumdirect.net/eric/CommandKit"
|
|
|
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
|
|
|
"go.yaml.in/yaml/v4"
|
|
|
|
|
)
|
2026-10-07 14:05:26 +00:00
|
|
|
|
|
|
|
|
type sourceReport struct {
|
|
|
|
|
Scope string `yaml:"scope"`
|
|
|
|
|
Sources []sourceDTO `yaml:"sources"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type sourceDTO struct {
|
2026-10-17 18:31:11 +00:00
|
|
|
ID string `yaml:"id"`
|
|
|
|
|
Label string `yaml:"label"`
|
|
|
|
|
Path string `yaml:"path"`
|
|
|
|
|
Destination string `yaml:"destination"`
|
|
|
|
|
Placement string `yaml:"placement"`
|
|
|
|
|
DestinationUnresolved bool `yaml:"destination_unresolved"`
|
|
|
|
|
Layer string `yaml:"layer"`
|
|
|
|
|
Kind string `yaml:"kind"`
|
|
|
|
|
UnderlayIndex int `yaml:"underlay_index"`
|
|
|
|
|
ImportedFrom *locationDTO `yaml:"imported_from"`
|
|
|
|
|
VersionCheck *sourceVersionCheckDTO `yaml:"version_check"`
|
2026-10-07 14:05:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type locationDTO struct {
|
|
|
|
|
SourceID string `yaml:"source_id"`
|
|
|
|
|
Line int `yaml:"line"`
|
|
|
|
|
Column int `yaml:"column"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-17 18:31:11 +00:00
|
|
|
type sourceVersionCheckDTO struct {
|
|
|
|
|
Path string `yaml:"path"`
|
|
|
|
|
Version versionDTO `yaml:"version"`
|
|
|
|
|
Location *locationDTO `yaml:"location"`
|
|
|
|
|
Redacted bool `yaml:"redacted"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type versionDTO struct {
|
|
|
|
|
Present bool `yaml:"present"`
|
|
|
|
|
Null bool `yaml:"null"`
|
|
|
|
|
Text string `yaml:"text"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:39:23 +00:00
|
|
|
func runConfigSources[I, S any](invocation commandkit.Invocation, plan *configOperationPlan[I, S], state S) commandkit.Outcome {
|
|
|
|
|
selected := plan.scopes[0]
|
|
|
|
|
name := selected.scope.scope.Name
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
2026-10-11 01:39:23 +00:00
|
|
|
return configOperationFailure(err, nil, false)
|
2026-10-07 16:47:28 +00:00
|
|
|
}
|
2026-10-11 01:39:23 +00:00
|
|
|
if selected.scope.scope.Acquire == nil {
|
|
|
|
|
return configOperationFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), nil, false)
|
2026-10-07 14:05:26 +00:00
|
|
|
}
|
2026-10-11 01:39:23 +00:00
|
|
|
acquired, acquireErr := selected.scope.scope.Acquire(invocation.Context, Request{
|
|
|
|
|
Operation: Sources,
|
|
|
|
|
Selection: plan.selection,
|
|
|
|
|
}, selected.input, state)
|
|
|
|
|
diagnostics := append([]commandkit.Diagnostic(nil), acquired.Diagnostics...)
|
2026-10-07 14:05:26 +00:00
|
|
|
if acquireErr != nil {
|
2026-10-11 01:39:23 +00:00
|
|
|
return configOperationFailure(acquireErr, diagnostics, false)
|
2026-10-07 14:05:26 +00:00
|
|
|
}
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
2026-10-11 01:39:23 +00:00
|
|
|
return configOperationFailure(err, diagnostics, false)
|
2026-10-07 16:47:28 +00:00
|
|
|
}
|
2026-10-07 14:05:26 +00:00
|
|
|
if acquired.Value.Sources == nil {
|
2026-10-11 01:39:23 +00:00
|
|
|
return configOperationFailure(fmt.Errorf("configuration scope %q did not provide a source reader", name), diagnostics, false)
|
2026-10-07 14:05:26 +00:00
|
|
|
}
|
2026-10-11 01:39:23 +00:00
|
|
|
|
2026-10-07 14:05:26 +00:00
|
|
|
sources := acquired.Value.Sources.Sources()
|
|
|
|
|
document := sourceReport{Scope: name, Sources: make([]sourceDTO, 0, len(sources))}
|
|
|
|
|
for _, source := range sources {
|
2026-10-17 18:31:11 +00:00
|
|
|
placement := "mapping"
|
|
|
|
|
if source.PlacementMode == configkit.PlacementModeSequenceItem {
|
|
|
|
|
placement = "sequence_item"
|
|
|
|
|
}
|
2026-10-07 14:05:26 +00:00
|
|
|
item := sourceDTO{
|
2026-10-17 18:31:11 +00:00
|
|
|
ID: source.ID,
|
|
|
|
|
Label: source.Label,
|
|
|
|
|
Path: source.Path,
|
|
|
|
|
Destination: source.Destination.String(),
|
|
|
|
|
Placement: placement,
|
|
|
|
|
DestinationUnresolved: source.DestinationUnresolved,
|
|
|
|
|
Layer: string(source.Layer),
|
|
|
|
|
Kind: string(source.Kind),
|
|
|
|
|
UnderlayIndex: source.UnderlayIndex,
|
2026-10-07 14:05:26 +00:00
|
|
|
}
|
|
|
|
|
if source.ImportedFrom != nil {
|
|
|
|
|
item.ImportedFrom = &locationDTO{SourceID: source.ImportedFrom.SourceID, Line: source.ImportedFrom.Line, Column: source.ImportedFrom.Column}
|
|
|
|
|
}
|
2026-10-17 18:31:11 +00:00
|
|
|
if source.VersionCheck != nil {
|
|
|
|
|
check := source.VersionCheck
|
|
|
|
|
item.VersionCheck = &sourceVersionCheckDTO{
|
|
|
|
|
Path: check.Path.String(),
|
|
|
|
|
Version: versionDTO{
|
|
|
|
|
Present: check.Version.Present,
|
|
|
|
|
Null: check.Version.Null,
|
|
|
|
|
Text: check.Version.Text,
|
|
|
|
|
},
|
|
|
|
|
Redacted: check.Redacted,
|
|
|
|
|
}
|
|
|
|
|
if check.Location != nil {
|
|
|
|
|
item.VersionCheck.Location = &locationDTO{SourceID: check.Location.SourceID, Line: check.Location.Line, Column: check.Location.Column}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-07 14:05:26 +00:00
|
|
|
document.Sources = append(document.Sources, item)
|
|
|
|
|
}
|
|
|
|
|
var report bytes.Buffer
|
2026-10-11 01:39:23 +00:00
|
|
|
if plan.format == "yaml" {
|
2026-10-07 14:05:26 +00:00
|
|
|
if err := encodeYAML(&report, document); err != nil {
|
2026-10-11 01:39:23 +00:00
|
|
|
return configOperationFailure(err, diagnostics, false)
|
2026-10-07 14:05:26 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
writeSourceReportText(&report, document)
|
|
|
|
|
}
|
2026-10-07 16:47:28 +00:00
|
|
|
if err := invocation.Context.Err(); err != nil {
|
2026-10-11 01:39:23 +00:00
|
|
|
return configOperationFailure(err, diagnostics, false)
|
2026-10-07 16:47:28 +00:00
|
|
|
}
|
2026-10-11 01:39:23 +00:00
|
|
|
if err := writeConfigReport(invocation.Streams.Out, report.Bytes()); err != nil {
|
|
|
|
|
return configDeliveryFailure(err, diagnostics, true)
|
2026-10-07 14:05:26 +00:00
|
|
|
}
|
2026-10-11 01:39:23 +00:00
|
|
|
outcome := commandkit.Success()
|
2026-10-07 14:05:26 +00:00
|
|
|
outcome.Diagnostics = diagnostics
|
|
|
|
|
return outcome
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeSourceReportText(writer *bytes.Buffer, document sourceReport) {
|
|
|
|
|
_, _ = fmt.Fprintf(writer, "Scope: %s\n", strconv.Quote(document.Scope))
|
|
|
|
|
if len(document.Sources) == 0 {
|
|
|
|
|
_, _ = writer.WriteString("Sources: []\n")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, _ = writer.WriteString("Sources:\n")
|
|
|
|
|
for _, source := range document.Sources {
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " - id: %s\n", strconv.Quote(source.ID))
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " label: %s\n", strconv.Quote(source.Label))
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " path: %s\n", strconv.Quote(source.Path))
|
2026-10-17 18:31:11 +00:00
|
|
|
_, _ = fmt.Fprintf(writer, " destination: %s\n", strconv.Quote(source.Destination))
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " placement: %s\n", strconv.Quote(source.Placement))
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " destination_unresolved: %t\n", source.DestinationUnresolved)
|
2026-10-07 14:05:26 +00:00
|
|
|
_, _ = fmt.Fprintf(writer, " layer: %s\n", strconv.Quote(source.Layer))
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " kind: %s\n", strconv.Quote(source.Kind))
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " underlay_index: %d\n", source.UnderlayIndex)
|
|
|
|
|
if source.ImportedFrom == nil {
|
|
|
|
|
_, _ = writer.WriteString(" imported_from: null\n")
|
|
|
|
|
} else {
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " imported_from: {source_id: %s, line: %d, column: %d}\n",
|
|
|
|
|
strconv.Quote(source.ImportedFrom.SourceID), source.ImportedFrom.Line, source.ImportedFrom.Column)
|
|
|
|
|
}
|
2026-10-17 18:31:11 +00:00
|
|
|
if source.VersionCheck == nil {
|
|
|
|
|
_, _ = writer.WriteString(" version_check: null\n")
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
check := source.VersionCheck
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " version_check:\n path: %s\n", strconv.Quote(check.Path))
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " version:\n present: %t\n null: %t\n text: %s\n",
|
|
|
|
|
check.Version.Present, check.Version.Null, strconv.Quote(check.Version.Text))
|
|
|
|
|
if check.Location == nil {
|
|
|
|
|
_, _ = writer.WriteString(" location: null\n")
|
|
|
|
|
} else {
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " location: {source_id: %s, line: %d, column: %d}\n",
|
|
|
|
|
strconv.Quote(check.Location.SourceID), check.Location.Line, check.Location.Column)
|
|
|
|
|
}
|
|
|
|
|
_, _ = fmt.Fprintf(writer, " redacted: %t\n", check.Redacted)
|
2026-10-07 14:05:26 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 01:39:23 +00:00
|
|
|
func encodeYAML(writer io.Writer, document any) error {
|
|
|
|
|
encoder := yaml.NewEncoder(writer)
|
|
|
|
|
encoder.SetIndent(2)
|
|
|
|
|
return encoder.Encode(document)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2026-10-07 14:05:26 +00:00
|
|
|
}
|