- Add internal/config/config.go with viper-backed Config struct - Explicit BindEnv calls for reliable env var -> config mapping (mapstructure v2 compat) - Config loads from config.json + .env, env vars take precedence - Add config.json with non-secret defaults (port, timeouts, URLs) - Fix SPA fallback: spaHandler serves index.html for unknown paths (client-side routing) - All 5 tests pass: TestHealth, TestLoadDefaults, TestLoadEnvOverride, TestLoadNetBoxURL - Add Makefile with build/dev/test/clean targets
77 lines
2.5 KiB
Go
77 lines
2.5 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/joho/godotenv"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
type Config struct {
|
|
Host string `mapstructure:"host"`
|
|
Port int `mapstructure:"port"`
|
|
LogLevel string `mapstructure:"log_level"`
|
|
|
|
NetBoxURL string `mapstructure:"netbox_url"`
|
|
NetBoxToken string `mapstructure:"netbox_token"`
|
|
NetBoxTimeoutSeconds int `mapstructure:"netbox_timeout_seconds"`
|
|
|
|
DragonflyURL string `mapstructure:"dragonfly_url"`
|
|
WAQRetryIntervalSeconds int `mapstructure:"waq_retry_interval_seconds"`
|
|
WAQMaxAttempts int `mapstructure:"waq_max_attempts"`
|
|
|
|
QualityGateConfidenceThreshold float64 `mapstructure:"quality_gate_confidence_threshold"`
|
|
}
|
|
|
|
func Load() (*Config, error) {
|
|
// Load .env file if present (ignore error — .env is optional in production)
|
|
_ = godotenv.Load()
|
|
|
|
v := viper.New()
|
|
|
|
// Set defaults
|
|
v.SetDefault("host", "0.0.0.0")
|
|
v.SetDefault("port", 8080)
|
|
v.SetDefault("log_level", "info")
|
|
v.SetDefault("netbox_timeout_seconds", 10)
|
|
v.SetDefault("waq_retry_interval_seconds", 30)
|
|
v.SetDefault("waq_max_attempts", 5)
|
|
v.SetDefault("quality_gate_confidence_threshold", 0.75)
|
|
|
|
// Config file
|
|
v.SetConfigName("config")
|
|
v.SetConfigType("json")
|
|
v.AddConfigPath(".")
|
|
v.AddConfigPath("/etc/hwlab")
|
|
|
|
// Environment variables: HWLAB_PORT -> port, HWLAB_NETBOX_URL -> netbox_url, etc.
|
|
v.SetEnvPrefix("HWLAB")
|
|
v.AutomaticEnv()
|
|
|
|
// Explicit bindings ensure AutomaticEnv works correctly with mapstructure v2 unmarshalling.
|
|
// Format: key name -> env var (without prefix, viper adds HWLAB_ automatically).
|
|
_ = v.BindEnv("host", "HWLAB_HOST")
|
|
_ = v.BindEnv("port", "HWLAB_PORT")
|
|
_ = v.BindEnv("log_level", "HWLAB_LOG_LEVEL")
|
|
_ = v.BindEnv("netbox_url", "HWLAB_NETBOX_URL")
|
|
_ = v.BindEnv("netbox_token", "HWLAB_NETBOX_TOKEN")
|
|
_ = v.BindEnv("netbox_timeout_seconds", "HWLAB_NETBOX_TIMEOUT_SECONDS")
|
|
_ = v.BindEnv("dragonfly_url", "HWLAB_DRAGONFLY_URL")
|
|
_ = v.BindEnv("waq_retry_interval_seconds", "HWLAB_WAQ_RETRY_INTERVAL_SECONDS")
|
|
_ = v.BindEnv("waq_max_attempts", "HWLAB_WAQ_MAX_ATTEMPTS")
|
|
_ = v.BindEnv("quality_gate_confidence_threshold", "HWLAB_QUALITY_GATE_CONFIDENCE_THRESHOLD")
|
|
|
|
// Read config file (non-fatal if missing)
|
|
if err := v.ReadInConfig(); err != nil {
|
|
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
|
return nil, fmt.Errorf("config file: %w", err)
|
|
}
|
|
}
|
|
|
|
var cfg Config
|
|
if err := v.Unmarshal(&cfg); err != nil {
|
|
return nil, fmt.Errorf("unmarshal config: %w", err)
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|