Add configuration source reports
This commit is contained in:
@@ -22,6 +22,155 @@ func (document effectiveYAML) WriteEffective(writer io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
type capturedSourceReader struct {
|
||||
entries []configkit.SourceInfo
|
||||
calls int
|
||||
}
|
||||
|
||||
func (reader *capturedSourceReader) Sources() []configkit.SourceInfo {
|
||||
reader.calls++
|
||||
return reader.entries
|
||||
}
|
||||
|
||||
func TestConfigSourcesReportsCapturedConfigKitSourceInventory(t *testing.T) {
|
||||
type configuration struct {
|
||||
Value string `yaml:"value"`
|
||||
Shared string `yaml:"shared"`
|
||||
Synthetic string `yaml:"synthetic"`
|
||||
}
|
||||
filesystem := configkit.NewFilesystem("fixture", fstest.MapFS{
|
||||
"root/config/main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: ../shared.yml}]}\nvalue: base\n")},
|
||||
"root/shared.yml": &fstest.MapFile{Data: []byte("shared: imported\n")},
|
||||
"root/overlays/dev.yml": &fstest.MapFile{Data: []byte("value: overlay\n")},
|
||||
})
|
||||
filesystemContext := &configkit.FilesystemContext{Root: "root", FS: &filesystem}
|
||||
overlayFilesystemContext := &configkit.FilesystemContext{Root: "root", BaseDir: "root/config", FS: &filesystem}
|
||||
loaderCalls := 0
|
||||
reader := &capturedSourceReader{}
|
||||
scope, err := commandkit.NewConfigKitScope[struct{}, configuration]("app", "", func(context.Context, commandkit.Selection, struct{}) (*configkit.Result[configuration], error) {
|
||||
loaderCalls++
|
||||
result, err := configkit.LoadLayered(configkit.Input{
|
||||
Source: filesystem.File("root/config/main.yml"),
|
||||
Imports: true,
|
||||
Filesystem: filesystemContext,
|
||||
}, configkit.LayeredOptions[configuration]{
|
||||
Definition: configkit.Definition[configuration]{},
|
||||
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Mapping("synthetic source", map[string]any{"synthetic": "underlay"})}}}},
|
||||
Overlay: "dev",
|
||||
OverlayFilesystem: overlayFilesystemContext,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader.entries = result.Sources()
|
||||
return result, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Replace the result's reader after the owning loader has captured its source inventory.
|
||||
originalAcquire := scope.Acquire
|
||||
scope.Acquire = func(ctx context.Context, request commandkit.ConfigRequest, input struct{}) (commandkit.Acquired[commandkit.ConfigView], error) {
|
||||
acquired, err := originalAcquire(ctx, request, input)
|
||||
if err == nil {
|
||||
acquired.Value.Sources = reader
|
||||
}
|
||||
return acquired, err
|
||||
}
|
||||
// Register the wrapped scope so the command reads the captured public facet once.
|
||||
app, _ := commandkit.New(commandkit.Identity{Name: "example"})
|
||||
if err := commandkit.RegisterConfig(app, commandkit.ConfigSuite[struct{}]{Scopes: []commandkit.ConfigScope[struct{}]{scope}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var output bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), []string{"config", "sources", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeSuccess || loaderCalls != 1 || reader.calls != 1 {
|
||||
var details bytes.Buffer
|
||||
for _, diagnostic := range outcome.Diagnostics {
|
||||
_ = diagnostic.Write(&details)
|
||||
}
|
||||
t.Fatalf("sources outcome=%#v cause=%v details=%q loader calls=%d reader calls=%d", outcome, outcome.Cause, details.String(), loaderCalls, reader.calls)
|
||||
}
|
||||
var report struct {
|
||||
Scope string `yaml:"scope"`
|
||||
Sources []struct {
|
||||
ID string `yaml:"id"`
|
||||
Label string `yaml:"label"`
|
||||
Path string `yaml:"path"`
|
||||
Layer string `yaml:"layer"`
|
||||
Kind string `yaml:"kind"`
|
||||
UnderlayIndex int `yaml:"underlay_index"`
|
||||
ImportedFrom *struct {
|
||||
SourceID string `yaml:"source_id"`
|
||||
Line int `yaml:"line"`
|
||||
Column int `yaml:"column"`
|
||||
} `yaml:"imported_from"`
|
||||
} `yaml:"sources"`
|
||||
}
|
||||
if err := yaml.Unmarshal(output.Bytes(), &report); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if report.Scope != "app" || len(report.Sources) != 4 {
|
||||
t.Fatalf("source report = %#v", report)
|
||||
}
|
||||
if report.Sources[0].Path != "root/config/main.yml" || report.Sources[0].ID != "source-1" {
|
||||
t.Fatalf("main source = %#v", report.Sources[0])
|
||||
}
|
||||
if report.Sources[0].ImportedFrom != nil {
|
||||
t.Fatalf("root source imported_from = %#v, want null", report.Sources[0].ImportedFrom)
|
||||
}
|
||||
imported := report.Sources[1]
|
||||
if imported.Path != "root/shared.yml" || imported.ImportedFrom == nil || imported.ImportedFrom.SourceID != report.Sources[0].ID || imported.ImportedFrom.Line == 0 {
|
||||
t.Fatalf("import source correlation = %#v", imported)
|
||||
}
|
||||
synthetic := report.Sources[2]
|
||||
if synthetic.Kind != string(configkit.SourceKindMapping) || synthetic.Path != "" || synthetic.Layer != string(configkit.LayerUnderlay) || synthetic.UnderlayIndex != 1 {
|
||||
t.Fatalf("synthetic underlay source = %#v", synthetic)
|
||||
}
|
||||
if report.Sources[3].Layer != string(configkit.LayerOverlay) || report.Sources[3].Path != "root/overlays/dev.yml" {
|
||||
t.Fatalf("overlay source = %#v", report.Sources[3])
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigSourcesHandlesEmptyInventoryAllPreflightAndWriterFailure(t *testing.T) {
|
||||
reader := &capturedSourceReader{}
|
||||
newContextCalls, acquireCalls := 0, 0
|
||||
app, err := commandkit.New(commandkit.Identity{Name: "example"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
suite := commandkit.ConfigSuite[string]{
|
||||
NewContext: func() string { newContextCalls++; return "context" },
|
||||
Scopes: []commandkit.ConfigScope[string]{{Name: "app", Operations: []commandkit.ConfigOperation{commandkit.ConfigSources}, Acquire: func(context.Context, commandkit.ConfigRequest, string) (commandkit.Acquired[commandkit.ConfigView], error) {
|
||||
acquireCalls++
|
||||
return commandkit.Acquired[commandkit.ConfigView]{Value: commandkit.ConfigView{Sources: reader}}, nil
|
||||
}}},
|
||||
}
|
||||
if err := commandkit.RegisterConfig(app, suite); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var output bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), []string{"config", "sources", "--scope", "all"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeUsage || newContextCalls != 0 || acquireCalls != 0 || output.Len() != 0 {
|
||||
t.Fatalf("all preflight outcome=%#v contexts=%d acquires=%d output=%q", outcome, newContextCalls, acquireCalls, output.String())
|
||||
}
|
||||
outcome = app.Execute(context.Background(), []string{"config", "sources", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeSuccess || output.String() != "scope: app\nsources: []\n" || reader.calls != 1 {
|
||||
t.Fatalf("empty source report outcome=%#v output=%q reader calls=%d", outcome, output.String(), reader.calls)
|
||||
}
|
||||
reader.entries = []configkit.SourceInfo{{ID: "source-1", Label: "label with \"quotes\"", Path: "path with spaces.yml", Layer: configkit.LayerBase, Kind: configkit.SourceKindFile}}
|
||||
var textOutput bytes.Buffer
|
||||
outcome = app.Execute(context.Background(), []string{"config", "sources"}, commandkit.Streams{Out: &textOutput, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(textOutput.String(), `label: "label with \"quotes\""`) || !strings.Contains(textOutput.String(), `path: "path with spaces.yml"`) {
|
||||
t.Fatalf("quoted text source output outcome=%#v output=%q", outcome, textOutput.String())
|
||||
}
|
||||
partial := &configKitShowPartialWriter{failure: io.ErrClosedPipe}
|
||||
outcome = app.Execute(context.Background(), []string{"config", "sources"}, commandkit.Streams{Out: partial, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeOperational || !outcome.ReportIncomplete || !errors.Is(outcome.DeliveryError, io.ErrClosedPipe) {
|
||||
t.Fatalf("source writer failure = %#v", outcome)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShowWritesNativeSingleScopeAndFramesAggregateYAML(t *testing.T) {
|
||||
const native = "%YAML 1.1\n# authored comment\n---\nvalue: null\nitems: []\nmissing: {}\n"
|
||||
app, err := commandkit.New(commandkit.Identity{Name: "example"})
|
||||
|
||||
Reference in New Issue
Block a user