Move ExpandPath function and path-related utilities from internal/config to internal/storage where they belong. Files updated: - internal/worker/config.go: use storage.ExpandPath - internal/network/ssh.go: use storage.ExpandPath - cmd/data_manager/data_manager_config.go: use storage.ExpandPath - internal/api/server_config.go: use storage.ExpandPath internal/storage/paths.go already contained the canonical implementation. Result: Path utilities now live in storage layer, config package focuses on configuration structs.
201 lines
5.7 KiB
Go
201 lines
5.7 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/auth"
|
|
"github.com/jfraeys/fetch_ml/internal/config"
|
|
"github.com/jfraeys/fetch_ml/internal/fileutil"
|
|
"github.com/jfraeys/fetch_ml/internal/logging"
|
|
"github.com/jfraeys/fetch_ml/internal/storage"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type QueueConfig struct {
|
|
Backend string `yaml:"backend"`
|
|
SQLitePath string `yaml:"sqlite_path"`
|
|
FilesystemPath string `yaml:"filesystem_path"`
|
|
FallbackToFilesystem bool `yaml:"fallback_to_filesystem"`
|
|
}
|
|
|
|
// ServerConfig holds all server configuration
|
|
type ServerConfig struct {
|
|
BasePath string `yaml:"base_path"`
|
|
DataDir string `yaml:"data_dir"`
|
|
Auth auth.Config `yaml:"auth"`
|
|
Server ServerSection `yaml:"server"`
|
|
Security SecurityConfig `yaml:"security"`
|
|
Monitoring MonitoringConfig `yaml:"monitoring"`
|
|
Queue QueueConfig `yaml:"queue"`
|
|
Redis RedisConfig `yaml:"redis"`
|
|
Database DatabaseConfig `yaml:"database"`
|
|
Logging logging.Config `yaml:"logging"`
|
|
Resources config.ResourceConfig `yaml:"resources"`
|
|
}
|
|
|
|
// ServerSection holds server-specific configuration
|
|
type ServerSection struct {
|
|
Address string `yaml:"address"`
|
|
TLS TLSConfig `yaml:"tls"`
|
|
}
|
|
|
|
// TLSConfig holds TLS configuration
|
|
type TLSConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
CertFile string `yaml:"cert_file"`
|
|
KeyFile string `yaml:"key_file"`
|
|
}
|
|
|
|
// SecurityConfig holds security-related configuration
|
|
type SecurityConfig struct {
|
|
ProductionMode bool `yaml:"production_mode"`
|
|
AllowedOrigins []string `yaml:"allowed_origins"`
|
|
APIKeyRotationDays int `yaml:"api_key_rotation_days"`
|
|
AuditLogging AuditLog `yaml:"audit_logging"`
|
|
RateLimit RateLimitConfig `yaml:"rate_limit"`
|
|
IPWhitelist []string `yaml:"ip_whitelist"`
|
|
FailedLockout LockoutConfig `yaml:"failed_login_lockout"`
|
|
}
|
|
|
|
// AuditLog holds audit logging configuration
|
|
type AuditLog struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
LogPath string `yaml:"log_path"`
|
|
}
|
|
|
|
// RateLimitConfig holds rate limiting configuration
|
|
type RateLimitConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
RequestsPerMinute int `yaml:"requests_per_minute"`
|
|
BurstSize int `yaml:"burst_size"`
|
|
}
|
|
|
|
// LockoutConfig holds failed login lockout configuration
|
|
type LockoutConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
MaxAttempts int `yaml:"max_attempts"`
|
|
LockoutDuration string `yaml:"lockout_duration"`
|
|
}
|
|
|
|
// RedisConfig holds Redis connection configuration
|
|
type RedisConfig struct {
|
|
Addr string `yaml:"addr"`
|
|
Password string `yaml:"password"`
|
|
DB int `yaml:"db"`
|
|
URL string `yaml:"url"`
|
|
}
|
|
|
|
// DatabaseConfig holds database connection configuration
|
|
type DatabaseConfig struct {
|
|
Type string `yaml:"type"`
|
|
Connection string `yaml:"connection"`
|
|
Host string `yaml:"host"`
|
|
Port int `yaml:"port"`
|
|
Username string `yaml:"username"`
|
|
Password string `yaml:"password"`
|
|
Database string `yaml:"database"`
|
|
}
|
|
|
|
// LoadServerConfig loads and validates server configuration
|
|
func LoadServerConfig(path string) (*ServerConfig, error) {
|
|
resolvedConfig, err := config.ResolveConfigPath(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfg, err := loadConfigFromFile(resolvedConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfg.Resources.ApplyDefaults()
|
|
return cfg, nil
|
|
}
|
|
|
|
// loadConfigFromFile loads configuration from a YAML file
|
|
func loadConfigFromFile(path string) (*ServerConfig, error) {
|
|
data, err := secureFileRead(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var cfg ServerConfig
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
// secureFileRead safely reads a file
|
|
func secureFileRead(path string) ([]byte, error) {
|
|
return fileutil.SecureFileRead(path)
|
|
}
|
|
|
|
// EnsureLogDirectory creates the log directory if needed
|
|
func (c *ServerConfig) EnsureLogDirectory() error {
|
|
if c.Logging.File == "" {
|
|
return nil
|
|
}
|
|
|
|
logDir := filepath.Dir(c.Logging.File)
|
|
log.Printf("Creating log directory: %s", logDir)
|
|
return os.MkdirAll(logDir, 0750)
|
|
}
|
|
|
|
// BuildAuthConfig creates the auth configuration
|
|
func (c *ServerConfig) BuildAuthConfig() *auth.Config {
|
|
if !c.Auth.Enabled {
|
|
return nil
|
|
}
|
|
|
|
log.Printf("Authentication enabled with %d API keys", len(c.Auth.APIKeys))
|
|
return &c.Auth
|
|
}
|
|
|
|
// Validate performs basic configuration validation
|
|
func (c *ServerConfig) Validate() error {
|
|
// Add validation logic here
|
|
if c.Server.Address == "" {
|
|
c.Server.Address = ":8080"
|
|
}
|
|
|
|
if c.BasePath == "" {
|
|
c.BasePath = "/tmp/ml-experiments"
|
|
}
|
|
if c.DataDir == "" {
|
|
c.DataDir = config.DefaultDataDir
|
|
}
|
|
|
|
backend := strings.ToLower(strings.TrimSpace(c.Queue.Backend))
|
|
if backend == "" {
|
|
backend = "redis"
|
|
c.Queue.Backend = backend
|
|
}
|
|
if backend != "redis" && backend != "sqlite" && backend != "filesystem" {
|
|
return fmt.Errorf("queue.backend must be one of 'redis', 'sqlite', or 'filesystem'")
|
|
}
|
|
if backend == "sqlite" {
|
|
if strings.TrimSpace(c.Queue.SQLitePath) == "" {
|
|
c.Queue.SQLitePath = filepath.Join(c.DataDir, "queue.db")
|
|
}
|
|
c.Queue.SQLitePath = storage.ExpandPath(c.Queue.SQLitePath)
|
|
if !filepath.IsAbs(c.Queue.SQLitePath) {
|
|
c.Queue.SQLitePath = filepath.Join(config.DefaultLocalDataDir, c.Queue.SQLitePath)
|
|
}
|
|
}
|
|
if backend == "filesystem" || c.Queue.FallbackToFilesystem {
|
|
if strings.TrimSpace(c.Queue.FilesystemPath) == "" {
|
|
c.Queue.FilesystemPath = filepath.Join(c.DataDir, "queue-fs")
|
|
}
|
|
c.Queue.FilesystemPath = storage.ExpandPath(c.Queue.FilesystemPath)
|
|
if !filepath.IsAbs(c.Queue.FilesystemPath) {
|
|
c.Queue.FilesystemPath = filepath.Join(config.DefaultLocalDataDir, c.Queue.FilesystemPath)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|