LFG
Some checks failed
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
CI/CD Pipeline / Build Docker Image (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Create Release (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
CI/CD Pipeline / Build Docker Image (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Create Release (push) Has been cancelled
This commit is contained in:
206
internal/config/config.go
Normal file
206
internal/config/config.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config represents the application configuration
|
||||
type Config struct {
|
||||
Server ServerConfig `yaml:"server"`
|
||||
Proxy ProxyConfig `yaml:"proxy"`
|
||||
NAT NATConfig `yaml:"nat"`
|
||||
Logging LoggingConfig `yaml:"logging"`
|
||||
Monitor MonitorConfig `yaml:"monitor"`
|
||||
}
|
||||
|
||||
// ServerConfig represents server configuration
|
||||
type ServerConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
ReadTimeout int `yaml:"read_timeout"`
|
||||
WriteTimeout int `yaml:"write_timeout"`
|
||||
IdleTimeout int `yaml:"idle_timeout"`
|
||||
TLSCertFile string `yaml:"tls_cert_file,omitempty"`
|
||||
TLSKeyFile string `yaml:"tls_key_file,omitempty"`
|
||||
}
|
||||
|
||||
// ProxyConfig represents reverse proxy configuration
|
||||
type ProxyConfig struct {
|
||||
Targets []TargetConfig `yaml:"targets"`
|
||||
LoadBalancer string `yaml:"load_balancer"` // "roundrobin", "leastconn", "random"
|
||||
HealthCheckPath string `yaml:"health_check_path"`
|
||||
HealthCheckInterval int `yaml:"health_check_interval"`
|
||||
}
|
||||
|
||||
// TargetConfig represents a proxy target
|
||||
type TargetConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Address string `yaml:"address"`
|
||||
Protocol string `yaml:"protocol"` // "http", "https"
|
||||
Weight int `yaml:"weight"` // for weighted load balancing
|
||||
Healthy bool `yaml:"-"` // health status
|
||||
}
|
||||
|
||||
// NATConfig represents NAT traversal configuration
|
||||
type NATConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
STUNServer string `yaml:"stun_server,omitempty"`
|
||||
TURNServer string `yaml:"turn_server,omitempty"`
|
||||
TURNUsername string `yaml:"turn_username,omitempty"`
|
||||
TURNPassword string `yaml:"turn_password,omitempty"`
|
||||
}
|
||||
|
||||
// LoggingConfig represents logging configuration
|
||||
type LoggingConfig struct {
|
||||
Level string `yaml:"level"` // "debug", "info", "warn", "error"
|
||||
Format string `yaml:"format"` // "json", "text"
|
||||
Output string `yaml:"output"` // "stdout", "file"
|
||||
File string `yaml:"file,omitempty"`
|
||||
}
|
||||
|
||||
// MonitorConfig represents monitoring configuration
|
||||
type MonitorConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Port int `yaml:"port"`
|
||||
Path string `yaml:"path"`
|
||||
Auth bool `yaml:"auth"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
}
|
||||
|
||||
// Load loads configuration from file
|
||||
func Load(path string) (*Config, error) {
|
||||
// Check if file exists
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("configuration file not found: %s", path)
|
||||
}
|
||||
|
||||
// Read file
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read configuration file: %w", err)
|
||||
}
|
||||
|
||||
// Parse YAML
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse configuration: %w", err)
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
setDefaults(&config)
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// setDefaults sets default values for configuration
|
||||
func setDefaults(c *Config) {
|
||||
// Server defaults
|
||||
if c.Server.Port == 0 {
|
||||
c.Server.Port = 8080
|
||||
}
|
||||
if c.Server.ReadTimeout == 0 {
|
||||
c.Server.ReadTimeout = 30
|
||||
}
|
||||
if c.Server.WriteTimeout == 0 {
|
||||
c.Server.WriteTimeout = 30
|
||||
}
|
||||
if c.Server.IdleTimeout == 0 {
|
||||
c.Server.IdleTimeout = 60
|
||||
}
|
||||
|
||||
// Proxy defaults
|
||||
if c.Proxy.LoadBalancer == "" {
|
||||
c.Proxy.LoadBalancer = "roundrobin"
|
||||
}
|
||||
if c.Proxy.HealthCheckPath == "" {
|
||||
c.Proxy.HealthCheckPath = "/health"
|
||||
}
|
||||
if c.Proxy.HealthCheckInterval == 0 {
|
||||
c.Proxy.HealthCheckInterval = 30
|
||||
}
|
||||
|
||||
// NAT defaults
|
||||
if c.NAT.Enabled && c.NAT.STUNServer == "" {
|
||||
c.NAT.STUNServer = "stun:stun.l.google.com:19302"
|
||||
}
|
||||
|
||||
// Logging defaults
|
||||
if c.Logging.Level == "" {
|
||||
c.Logging.Level = "info"
|
||||
}
|
||||
if c.Logging.Format == "" {
|
||||
c.Logging.Format = "json"
|
||||
}
|
||||
if c.Logging.Output == "" {
|
||||
c.Logging.Output = "stdout"
|
||||
}
|
||||
|
||||
// Monitor defaults
|
||||
if c.Monitor.Enabled && c.Monitor.Port == 0 {
|
||||
c.Monitor.Port = 9090
|
||||
}
|
||||
if c.Monitor.Enabled && c.Monitor.Path == "" {
|
||||
c.Monitor.Path = "/metrics"
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDefaultConfig creates a default configuration file
|
||||
func CreateDefaultConfig(path string) error {
|
||||
config := Config{
|
||||
Server: ServerConfig{
|
||||
Port: 8080,
|
||||
ReadTimeout: 30,
|
||||
WriteTimeout: 30,
|
||||
IdleTimeout: 60,
|
||||
},
|
||||
Proxy: ProxyConfig{
|
||||
LoadBalancer: "roundrobin",
|
||||
HealthCheckPath: "/health",
|
||||
HealthCheckInterval: 30,
|
||||
Targets: []TargetConfig{
|
||||
{
|
||||
Name: "example",
|
||||
Address: "http://localhost:3000",
|
||||
Protocol: "http",
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
NAT: NATConfig{
|
||||
Enabled: false,
|
||||
STUNServer: "stun:stun.l.google.com:19302",
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
Output: "stdout",
|
||||
},
|
||||
Monitor: MonitorConfig{
|
||||
Enabled: true,
|
||||
Port: 9090,
|
||||
Path: "/metrics",
|
||||
Auth: false,
|
||||
},
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal default configuration: %w", err)
|
||||
}
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create configuration directory: %w", err)
|
||||
}
|
||||
|
||||
// Write configuration file
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write configuration file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
243
internal/config/config_test.go
Normal file
243
internal/config/config_test.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
// Create a temporary directory for test files
|
||||
tempDir := t.TempDir()
|
||||
configPath := filepath.Join(tempDir, "test-config.yaml")
|
||||
|
||||
// Create a test configuration file
|
||||
testConfig := `server:
|
||||
port: 9090
|
||||
read_timeout: 60
|
||||
write_timeout: 60
|
||||
idle_timeout: 120
|
||||
|
||||
proxy:
|
||||
targets:
|
||||
- name: "test-target"
|
||||
address: "http://localhost:8080"
|
||||
protocol: "http"
|
||||
weight: 1
|
||||
load_balancer: "leastconn"
|
||||
health_check_path: "/healthz"
|
||||
health_check_interval: 60
|
||||
|
||||
nat:
|
||||
enabled: true
|
||||
stun_server: "stun:stun.example.com:3478"
|
||||
turn_server: "turn:turn.example.com:3478"
|
||||
turn_username: "testuser"
|
||||
turn_password: "testpass"
|
||||
|
||||
logging:
|
||||
level: "debug"
|
||||
format: "text"
|
||||
output: "file"
|
||||
file: "/var/log/gorz.log"
|
||||
|
||||
monitor:
|
||||
enabled: true
|
||||
port: 8081
|
||||
path: "/stats"
|
||||
auth: true
|
||||
username: "admin"
|
||||
password: "secret"
|
||||
`
|
||||
|
||||
err := os.WriteFile(configPath, []byte(testConfig), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write test config file: %v", err)
|
||||
}
|
||||
|
||||
// Test loading the configuration
|
||||
cfg, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load configuration: %v", err)
|
||||
}
|
||||
|
||||
// Verify server configuration
|
||||
if cfg.Server.Port != 9090 {
|
||||
t.Errorf("Expected port 9090, got %d", cfg.Server.Port)
|
||||
}
|
||||
if cfg.Server.ReadTimeout != 60 {
|
||||
t.Errorf("Expected read timeout 60, got %d", cfg.Server.ReadTimeout)
|
||||
}
|
||||
|
||||
// Verify proxy configuration
|
||||
if cfg.Proxy.LoadBalancer != "leastconn" {
|
||||
t.Errorf("Expected load balancer 'leastconn', got %s", cfg.Proxy.LoadBalancer)
|
||||
}
|
||||
if len(cfg.Proxy.Targets) != 1 {
|
||||
t.Errorf("Expected 1 target, got %d", len(cfg.Proxy.Targets))
|
||||
}
|
||||
if cfg.Proxy.Targets[0].Name != "test-target" {
|
||||
t.Errorf("Expected target name 'test-target', got %s", cfg.Proxy.Targets[0].Name)
|
||||
}
|
||||
|
||||
// Verify NAT configuration
|
||||
if !cfg.NAT.Enabled {
|
||||
t.Error("Expected NAT enabled to be true")
|
||||
}
|
||||
if cfg.NAT.STUNServer != "stun:stun.example.com:3478" {
|
||||
t.Errorf("Expected STUN server 'stun:stun.example.com:3478', got %s", cfg.NAT.STUNServer)
|
||||
}
|
||||
|
||||
// Verify logging configuration
|
||||
if cfg.Logging.Level != "debug" {
|
||||
t.Errorf("Expected log level 'debug', got %s", cfg.Logging.Level)
|
||||
}
|
||||
if cfg.Logging.Output != "file" {
|
||||
t.Errorf("Expected log output 'file', got %s", cfg.Logging.Output)
|
||||
}
|
||||
|
||||
// Verify monitor configuration
|
||||
if !cfg.Monitor.Enabled {
|
||||
t.Error("Expected monitor enabled to be true")
|
||||
}
|
||||
if cfg.Monitor.Port != 8081 {
|
||||
t.Errorf("Expected monitor port 8081, got %d", cfg.Monitor.Port)
|
||||
}
|
||||
if !cfg.Monitor.Auth {
|
||||
t.Error("Expected monitor auth to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadNonexistentFile(t *testing.T) {
|
||||
_, err := Load("/nonexistent/path/config.yaml")
|
||||
if err == nil {
|
||||
t.Error("Expected error for nonexistent file, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadInvalidYAML(t *testing.T) {
|
||||
// Create a temporary directory for test files
|
||||
tempDir := t.TempDir()
|
||||
configPath := filepath.Join(tempDir, "invalid-config.yaml")
|
||||
|
||||
// Create an invalid YAML file
|
||||
invalidYAML := `server:
|
||||
port: 8080
|
||||
read_timeout: "not a number"
|
||||
`
|
||||
|
||||
err := os.WriteFile(configPath, []byte(invalidYAML), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write invalid config file: %v", err)
|
||||
}
|
||||
|
||||
_, err = Load(configPath)
|
||||
if err == nil {
|
||||
t.Error("Expected error for invalid YAML, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaults(t *testing.T) {
|
||||
cfg := &Config{}
|
||||
|
||||
// Apply defaults
|
||||
setDefaults(cfg)
|
||||
|
||||
// Verify default server values
|
||||
if cfg.Server.Port != 8080 {
|
||||
t.Errorf("Expected default port 8080, got %d", cfg.Server.Port)
|
||||
}
|
||||
if cfg.Server.ReadTimeout != 30 {
|
||||
t.Errorf("Expected default read timeout 30, got %d", cfg.Server.ReadTimeout)
|
||||
}
|
||||
|
||||
// Verify default proxy values
|
||||
if cfg.Proxy.LoadBalancer != "roundrobin" {
|
||||
t.Errorf("Expected default load balancer 'roundrobin', got %s", cfg.Proxy.LoadBalancer)
|
||||
}
|
||||
if cfg.Proxy.HealthCheckPath != "/health" {
|
||||
t.Errorf("Expected default health check path '/health', got %s", cfg.Proxy.HealthCheckPath)
|
||||
}
|
||||
|
||||
// Verify default NAT values
|
||||
if cfg.NAT.Enabled {
|
||||
t.Error("Expected default NAT enabled to be false")
|
||||
}
|
||||
if cfg.NAT.STUNServer != "stun:stun.l.google.com:19302" {
|
||||
t.Errorf("Expected default STUN server 'stun:stun.l.google.com:19302', got %s", cfg.NAT.STUNServer)
|
||||
}
|
||||
|
||||
// Verify default logging values
|
||||
if cfg.Logging.Level != "info" {
|
||||
t.Errorf("Expected default log level 'info', got %s", cfg.Logging.Level)
|
||||
}
|
||||
if cfg.Logging.Format != "json" {
|
||||
t.Errorf("Expected default log format 'json', got %s", cfg.Logging.Format)
|
||||
}
|
||||
|
||||
// Verify default monitor values
|
||||
if !cfg.Monitor.Enabled {
|
||||
t.Error("Expected default monitor enabled to be true")
|
||||
}
|
||||
if cfg.Monitor.Port != 9090 {
|
||||
t.Errorf("Expected default monitor port 9090, got %d", cfg.Monitor.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDefaultConfig(t *testing.T) {
|
||||
// Create a temporary directory for test files
|
||||
tempDir := t.TempDir()
|
||||
configPath := filepath.Join(tempDir, "default-config.yaml")
|
||||
|
||||
// Create default configuration
|
||||
err := CreateDefaultConfig(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create default configuration: %v", err)
|
||||
}
|
||||
|
||||
// Verify the file was created
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
t.Error("Expected config file to be created")
|
||||
}
|
||||
|
||||
// Load and verify the configuration
|
||||
cfg, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load created configuration: %v", err)
|
||||
}
|
||||
|
||||
// Verify some default values
|
||||
if cfg.Server.Port != 8080 {
|
||||
t.Errorf("Expected default port 8080, got %d", cfg.Server.Port)
|
||||
}
|
||||
if cfg.Proxy.LoadBalancer != "roundrobin" {
|
||||
t.Errorf("Expected default load balancer 'roundrobin', got %s", cfg.Proxy.LoadBalancer)
|
||||
}
|
||||
if cfg.Logging.Level != "info" {
|
||||
t.Errorf("Expected default log level 'info', got %s", cfg.Logging.Level)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDefaultConfigDirectoryCreation(t *testing.T) {
|
||||
// Create a temporary directory for test files
|
||||
tempDir := t.TempDir()
|
||||
nestedDir := filepath.Join(tempDir, "nested", "directory")
|
||||
configPath := filepath.Join(nestedDir, "config.yaml")
|
||||
|
||||
// Create default configuration in a nested directory
|
||||
err := CreateDefaultConfig(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create default configuration: %v", err)
|
||||
}
|
||||
|
||||
// Verify the directory was created
|
||||
if _, err := os.Stat(nestedDir); os.IsNotExist(err) {
|
||||
t.Error("Expected nested directory to be created")
|
||||
}
|
||||
|
||||
// Verify the file was created
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
t.Error("Expected config file to be created")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user