Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions internal/metrics/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package metrics
import (
"context"
_ "embed"
"io/fs"
"maps"
"os"
"path/filepath"
Expand Down Expand Up @@ -66,21 +65,21 @@ func (fmr *fileMetricReader) getMetrics() (metrics *Metrics, err error) {
}
switch mode := fi.Mode(); {
case mode.IsDir():
err = filepath.WalkDir(fmr.path, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
ext := strings.ToLower(filepath.Ext(d.Name()))
if d.IsDir() || ext != ".yaml" && ext != ".yml" {
return nil
var entries []os.DirEntry
if entries, err = os.ReadDir(fmr.path); err != nil {
return nil, err
}
for _, entry := range entries {
if ext := strings.ToLower(filepath.Ext(entry.Name())); ext != ".yaml" && ext != ".yml" {
continue
}
var m *Metrics
if m, err = fmr.loadMetricsFromFile(path); err == nil {
maps.Copy(metrics.PresetDefs, m.PresetDefs)
maps.Copy(metrics.MetricDefs, m.MetricDefs)
if m, err = fmr.loadMetricsFromFile(filepath.Join(fmr.path, entry.Name())); err != nil {
return nil, err
}
return err
})
maps.Copy(metrics.PresetDefs, m.PresetDefs)
maps.Copy(metrics.MetricDefs, m.MetricDefs)
}
case mode.IsRegular():
metrics, err = fmr.loadMetricsFromFile(fmr.path)
}
Expand Down
56 changes: 52 additions & 4 deletions internal/metrics/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -222,9 +223,9 @@ func TestErrorHandlingToFile(t *testing.T) {
err = fmr.WriteMetrics(&metrics.Metrics{})
assert.Error(t, err)

// Test GetMetrics
// Test GetMetrics - root dir has no yaml files, returns empty metrics without error
_, err = fmr.GetMetrics()
assert.Error(t, err)
assert.NoError(t, err)

// Test DeleteMetric
err = fmr.DeleteMetric("test")
Expand Down Expand Up @@ -375,11 +376,11 @@ func TestMetricsDir(t *testing.T) {
metrics2File, err := yaml.Marshal(metrics2)
a.NoError(err)

// write data to different files in a folder
// write data to different files in a folder: one .yaml and one .yml
tempDir := t.TempDir()
err = os.WriteFile(filepath.Join(tempDir, "metrics1.yaml"), metrics1File, 0644)
a.NoError(err)
err = os.WriteFile(filepath.Join(tempDir, "metrics2.yaml"), metrics2File, 0644)
err = os.WriteFile(filepath.Join(tempDir, "metrics2.yml"), metrics2File, 0644)
a.NoError(err)

// use folder of yaml files for metrics configs
Expand Down Expand Up @@ -474,3 +475,50 @@ func TestConcurrentPresetUpdates(t *testing.T) {
a.NoError(err)
a.Equal(numGoroutines, len(finalMetrics.PresetDefs), "Some updates were lost due to race condition!")
}

func TestGetMetricsNonExistentPath(t *testing.T) {
nonExistent := filepath.Join(t.TempDir(), "does_not_exist", "metrics.yaml")
fmr, err := metrics.NewYAMLMetricReaderWriter(ctx, nonExistent)
assert.NoError(t, err)
_, err = fmr.GetMetrics()
assert.Error(t, err)
}

func TestGetMetricsDirWithInvalidYAML(t *testing.T) {
dir := t.TempDir()
err := os.WriteFile(filepath.Join(dir, "bad.yaml"), []byte("invalid: yaml: {unclosed"), 0644)
assert.NoError(t, err)
fmr, err := metrics.NewYAMLMetricReaderWriter(ctx, dir)
assert.NoError(t, err)
_, err = fmr.GetMetrics()
assert.Error(t, err)
}

func TestMutationsGetMetricsError(t *testing.T) {
nonExistent := filepath.Join(t.TempDir(), "does_not_exist", "metrics.yaml")
fmr, err := metrics.NewYAMLMetricReaderWriter(ctx, nonExistent)
assert.NoError(t, err)

assert.Error(t, fmr.DeleteMetric("x"))
assert.Error(t, fmr.UpdateMetric("x", metrics.Metric{}))
assert.Error(t, fmr.CreateMetric("x", metrics.Metric{}))
assert.Error(t, fmr.DeletePreset("x"))
assert.Error(t, fmr.UpdatePreset("x", metrics.Preset{}))
assert.Error(t, fmr.CreatePreset("x", metrics.Preset{}))
}

func TestLoadMetricsUnreadableFile(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("cannot reliably test file permissions on Windows")
}
if os.Getuid() == 0 {
t.Skip("running as root, permission checks do not apply")
}
f := filepath.Join(t.TempDir(), "metrics.yaml")
err := os.WriteFile(f, []byte(""), 0000)
assert.NoError(t, err)
fmr, err := metrics.NewYAMLMetricReaderWriter(ctx, f)
assert.NoError(t, err)
_, err = fmr.GetMetrics()
assert.Error(t, err)
}
22 changes: 11 additions & 11 deletions internal/sources/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,20 @@ func (fcr *fileSourcesReaderWriter) getSources() (dbs Sources, err error) {
}
switch mode := fi.Mode(); {
case mode.IsDir():
err = filepath.WalkDir(fcr.path, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
ext := strings.ToLower(filepath.Ext(d.Name()))
if d.IsDir() || ext != ".yaml" && ext != ".yml" {
return nil
var entries []os.DirEntry
if entries, err = os.ReadDir(fcr.path); err != nil {
return nil, err
}
for _, entry := range entries {
if ext := strings.ToLower(filepath.Ext(entry.Name())); ext != ".yaml" && ext != ".yml" {
continue
}
var mdbs Sources
if mdbs, err = fcr.loadSourcesFromFile(path); err == nil {
dbs = append(dbs, mdbs...)
if mdbs, err = fcr.loadSourcesFromFile(filepath.Join(fcr.path, entry.Name())); err != nil {
return
}
return err
})
dbs = append(dbs, mdbs...)
}
case mode.IsRegular():
dbs, err = fcr.loadSourcesFromFile(fcr.path)
}
Expand Down
52 changes: 52 additions & 0 deletions internal/sources/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,27 @@ func TestYAMLGetMonitoredDatabases(t *testing.T) {
a.Error(err)
a.Nil(dbs)
})

t.Run("directory with yaml and yml files", func(t *testing.T) {
tmpDir := t.TempDir()
yamlContent1 := `
- name: dir_test1
conn_str: postgresql://localhost/test1
`
yamlContent2 := `
- name: dir_test2
conn_str: postgresql://localhost/test2
`
err := os.WriteFile(filepath.Join(tmpDir, "sources.yaml"), []byte(yamlContent1), 0644)
a.NoError(err)
err = os.WriteFile(filepath.Join(tmpDir, "sources.yml"), []byte(yamlContent2), 0644)
a.NoError(err)
yamlrw, err := sources.NewYAMLSourcesReaderWriter(ctx, tmpDir)
a.NoError(err)
dbs, err := yamlrw.GetSources()
a.NoError(err)
a.Len(dbs, 2)
})
}

func TestYAMLDeleteDatabase(t *testing.T) {
Expand Down Expand Up @@ -364,3 +385,34 @@ func TestConcurrentSourceUpdates(t *testing.T) {
a.NoError(err)
a.Equal(numGoroutines, len(finalSources), "Some updates were lost due to race condition!")
}

func TestGetSourcesDirWithInvalidYAML(t *testing.T) {
dir := t.TempDir()
err := os.WriteFile(filepath.Join(dir, "bad.yaml"), []byte("invalid: yaml: {unclosed"), 0644)
assert.NoError(t, err)
yamlrw, err := sources.NewYAMLSourcesReaderWriter(ctx, dir)
assert.NoError(t, err)
_, err = yamlrw.GetSources()
assert.Error(t, err)
}

func TestCreateSourceGetSourcesError(t *testing.T) {
nonExistent := filepath.Join(t.TempDir(), "does_not_exist", "sources.yaml")
yamlrw, err := sources.NewYAMLSourcesReaderWriter(ctx, nonExistent)
assert.NoError(t, err)
assert.Error(t, yamlrw.CreateSource(sources.Source{Name: "x"}))
}

// TestMutationsWriteError verifies that UpdateSource, DeleteSource, and CreateSource
// propagate write errors when the path is a directory (cannot be overwritten as a file).
func TestMutationsWriteError(t *testing.T) {
// Using a dir as the file path: getSources succeeds (reads empty dir),
// but writeSources fails because os.WriteFile cannot write to a directory.
dir := t.TempDir()
yamlrw, err := sources.NewYAMLSourcesReaderWriter(ctx, dir)
assert.NoError(t, err)

assert.Error(t, yamlrw.UpdateSource(sources.Source{Name: "x"}))
assert.Error(t, yamlrw.DeleteSource("x"))
assert.Error(t, yamlrw.CreateSource(sources.Source{Name: "x"}))
}
Loading