Inject Config as dependency for database, routing table, and snapshotter

- Remove old database Config struct and related functions
- Update database.New() to accept config.Config parameter
- Update routingtable.New() to accept config.Config parameter
- Update snapshotter.New() to accept config.Config parameter
- Simplify fx module providers in app.go
- Fix truthiness check for environment variables
- Handle empty state directory gracefully in routing table and snapshotter
- Update all tests to use empty state directory for testing
This commit is contained in:
2025-07-28 00:55:09 +02:00
parent 1a0622efaa
commit d15a5e91b9
7 changed files with 174 additions and 174 deletions

View File

@@ -8,12 +8,12 @@ import (
"log/slog"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"git.eeqj.de/sneak/routewatch/internal/config"
"github.com/google/uuid"
)
@@ -23,7 +23,7 @@ const (
routeStalenessThreshold = 30 * time.Minute
// snapshotFilename is the name of the snapshot file
snapshotFilename = "routewatch-snapshot.json.gz"
snapshotFilename = "routingtable.json.gz"
)
// Route represents a single route entry in the routing table
@@ -62,16 +62,20 @@ type RoutingTable struct {
ipv4Updates uint64 // Updates counter for rate calculation
ipv6Updates uint64 // Updates counter for rate calculation
lastMetricsReset time.Time
// Configuration
snapshotDir string
}
// New creates a new routing table, loading from snapshot if available
func New(logger *slog.Logger) *RoutingTable {
func New(cfg *config.Config, logger *slog.Logger) *RoutingTable {
rt := &RoutingTable{
routes: make(map[RouteKey]*Route),
byPrefix: make(map[uuid.UUID]map[RouteKey]*Route),
byOriginASN: make(map[uuid.UUID]map[RouteKey]*Route),
byPeerASN: make(map[int]map[RouteKey]*Route),
lastMetricsReset: time.Now(),
snapshotDir: cfg.GetStateDir(),
}
// Try to load from snapshot
@@ -447,51 +451,18 @@ func isIPv6(prefix string) bool {
return strings.Contains(prefix, ":")
}
// getStateDirectory returns the appropriate state directory based on the OS
func getStateDirectory() (string, error) {
switch runtime.GOOS {
case "darwin":
// macOS: Use ~/Library/Application Support/routewatch
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, "Library", "Application Support", "routewatch"), nil
case "linux", "freebsd", "openbsd", "netbsd":
// Unix-like: Use /var/lib/routewatch if running as root, otherwise use XDG_STATE_HOME
if os.Geteuid() == 0 {
return "/var/lib/routewatch", nil
}
// Check XDG_STATE_HOME first
if xdgState := os.Getenv("XDG_STATE_HOME"); xdgState != "" {
return filepath.Join(xdgState, "routewatch"), nil
}
// Fall back to ~/.local/state/routewatch
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".local", "state", "routewatch"), nil
default:
return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
}
}
// loadFromSnapshot attempts to load the routing table from a snapshot file
func (rt *RoutingTable) loadFromSnapshot(logger *slog.Logger) error {
stateDir, err := getStateDirectory()
if err != nil {
return fmt.Errorf("failed to determine state directory: %w", err)
// If no snapshot directory specified, nothing to load
if rt.snapshotDir == "" {
return nil
}
snapshotPath := filepath.Join(stateDir, snapshotFilename)
snapshotPath := filepath.Join(rt.snapshotDir, snapshotFilename)
// Check if snapshot file exists
if _, err := os.Stat(snapshotPath); os.IsNotExist(err) {
logger.Info("No snapshot file found, starting with empty routing table")
// No snapshot file exists, this is normal - start with empty routing table
return nil
}

View File

@@ -6,13 +6,20 @@ import (
"testing"
"time"
"git.eeqj.de/sneak/routewatch/internal/config"
"github.com/google/uuid"
)
func TestRoutingTable(t *testing.T) {
// Create a test logger
logger := slog.Default()
rt := New(logger)
// Create test config with empty state dir (no snapshot loading)
cfg := &config.Config{
StateDir: "",
}
rt := New(cfg, logger)
// Test data
prefixID1 := uuid.New()
@@ -123,7 +130,13 @@ func TestRoutingTable(t *testing.T) {
func TestRoutingTableConcurrency(t *testing.T) {
// Create a test logger
logger := slog.Default()
rt := New(logger)
// Create test config with empty state dir (no snapshot loading)
cfg := &config.Config{
StateDir: "",
}
rt := New(cfg, logger)
// Test concurrent access
var wg sync.WaitGroup
@@ -177,7 +190,13 @@ func TestRoutingTableConcurrency(t *testing.T) {
func TestRouteUpdate(t *testing.T) {
// Create a test logger
logger := slog.Default()
rt := New(logger)
// Create test config with empty state dir (no snapshot loading)
cfg := &config.Config{
StateDir: "",
}
rt := New(cfg, logger)
prefixID := uuid.New()
originASNID := uuid.New()