fix(go.sum): update ResolveSpec dependency to v1.0.87
This commit is contained in:
+291
@@ -0,0 +1,291 @@
|
||||
# ResolveSpec Configuration System
|
||||
|
||||
A centralized configuration system with support for multiple configuration sources: config files (YAML, TOML, JSON), environment variables, and programmatic configuration.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Config Sources**: Config files, environment variables, and code
|
||||
- **Priority Order**: Environment variables > Config file > Defaults
|
||||
- **Multiple Formats**: YAML, TOML, JSON supported
|
||||
- **Type Safety**: Strongly-typed configuration structs
|
||||
- **Sensible Defaults**: Works out of the box with reasonable defaults
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```go
|
||||
import "github.com/heinhel/ResolveSpec/pkg/config"
|
||||
|
||||
// Create a new config manager
|
||||
mgr := config.NewManager()
|
||||
|
||||
// Load configuration from file and environment
|
||||
if err := mgr.Load(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Get the complete configuration
|
||||
cfg, err := mgr.GetConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Use the configuration
|
||||
fmt.Println("Server address:", cfg.Server.Addr)
|
||||
```
|
||||
|
||||
### Custom Configuration Paths
|
||||
|
||||
```go
|
||||
mgr := config.NewManagerWithOptions(
|
||||
config.WithConfigFile("/path/to/config.yaml"),
|
||||
config.WithEnvPrefix("MYAPP"),
|
||||
)
|
||||
```
|
||||
|
||||
## Configuration Sources
|
||||
|
||||
### 1. Config Files
|
||||
|
||||
Place a `config.yaml` file in one of these locations:
|
||||
- Current directory (`.`)
|
||||
- `./config/`
|
||||
- `/etc/resolvespec/`
|
||||
- `$HOME/.resolvespec/`
|
||||
|
||||
Example `config.yaml`:
|
||||
|
||||
```yaml
|
||||
server:
|
||||
addr: ":8080"
|
||||
shutdown_timeout: 30s
|
||||
|
||||
tracing:
|
||||
enabled: true
|
||||
service_name: "my-service"
|
||||
|
||||
cache:
|
||||
provider: "redis"
|
||||
redis:
|
||||
host: "localhost"
|
||||
port: 6379
|
||||
```
|
||||
|
||||
### 2. Environment Variables
|
||||
|
||||
All configuration can be set via environment variables with the `RESOLVESPEC_` prefix:
|
||||
|
||||
```bash
|
||||
export RESOLVESPEC_SERVER_ADDR=":9090"
|
||||
export RESOLVESPEC_TRACING_ENABLED=true
|
||||
export RESOLVESPEC_CACHE_PROVIDER=redis
|
||||
export RESOLVESPEC_CACHE_REDIS_HOST=localhost
|
||||
```
|
||||
|
||||
Nested configuration uses underscores:
|
||||
- `server.addr` → `RESOLVESPEC_SERVER_ADDR`
|
||||
- `cache.redis.host` → `RESOLVESPEC_CACHE_REDIS_HOST`
|
||||
|
||||
### 3. Programmatic Configuration
|
||||
|
||||
```go
|
||||
mgr := config.NewManager()
|
||||
mgr.Set("server.addr", ":9090")
|
||||
mgr.Set("tracing.enabled", true)
|
||||
|
||||
cfg, _ := mgr.GetConfig()
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Server Configuration
|
||||
|
||||
```yaml
|
||||
server:
|
||||
addr: ":8080" # Server address
|
||||
shutdown_timeout: 30s # Graceful shutdown timeout
|
||||
drain_timeout: 25s # Connection drain timeout
|
||||
read_timeout: 10s # HTTP read timeout
|
||||
write_timeout: 10s # HTTP write timeout
|
||||
idle_timeout: 120s # HTTP idle timeout
|
||||
```
|
||||
|
||||
### Tracing Configuration
|
||||
|
||||
```yaml
|
||||
tracing:
|
||||
enabled: false # Enable/disable tracing
|
||||
service_name: "resolvespec" # Service name
|
||||
service_version: "1.0.0" # Service version
|
||||
endpoint: "http://localhost:4318/v1/traces" # OTLP endpoint
|
||||
```
|
||||
|
||||
### Cache Configuration
|
||||
|
||||
```yaml
|
||||
cache:
|
||||
provider: "memory" # Options: memory, redis, memcache
|
||||
|
||||
redis:
|
||||
host: "localhost"
|
||||
port: 6379
|
||||
password: ""
|
||||
db: 0
|
||||
|
||||
memcache:
|
||||
servers:
|
||||
- "localhost:11211"
|
||||
max_idle_conns: 10
|
||||
timeout: 100ms
|
||||
```
|
||||
|
||||
### Logger Configuration
|
||||
|
||||
```yaml
|
||||
logger:
|
||||
dev: false # Development mode (human-readable output)
|
||||
path: "" # Log file path (empty = stdout)
|
||||
```
|
||||
|
||||
### Middleware Configuration
|
||||
|
||||
```yaml
|
||||
middleware:
|
||||
rate_limit_rps: 100.0 # Requests per second
|
||||
rate_limit_burst: 200 # Burst size
|
||||
max_request_size: 10485760 # Max request size in bytes (10MB)
|
||||
```
|
||||
|
||||
### CORS Configuration
|
||||
|
||||
```yaml
|
||||
cors:
|
||||
allowed_origins:
|
||||
- "*"
|
||||
allowed_methods:
|
||||
- "GET"
|
||||
- "POST"
|
||||
- "PUT"
|
||||
- "DELETE"
|
||||
- "OPTIONS"
|
||||
allowed_headers:
|
||||
- "*"
|
||||
max_age: 3600
|
||||
```
|
||||
|
||||
### Database Configuration
|
||||
|
||||
```yaml
|
||||
database:
|
||||
url: "host=localhost user=postgres password=postgres dbname=mydb port=5432 sslmode=disable"
|
||||
```
|
||||
|
||||
## Priority and Overrides
|
||||
|
||||
Configuration sources are applied in this order (highest priority first):
|
||||
|
||||
1. **Environment Variables** (highest priority)
|
||||
2. **Config File**
|
||||
3. **Defaults** (lowest priority)
|
||||
|
||||
This allows you to:
|
||||
- Set defaults in code
|
||||
- Override with a config file
|
||||
- Override specific values with environment variables
|
||||
|
||||
## Examples
|
||||
|
||||
### Production Setup
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
server:
|
||||
addr: ":8080"
|
||||
|
||||
tracing:
|
||||
enabled: true
|
||||
service_name: "myapi"
|
||||
endpoint: "http://jaeger:4318/v1/traces"
|
||||
|
||||
cache:
|
||||
provider: "redis"
|
||||
redis:
|
||||
host: "redis"
|
||||
port: 6379
|
||||
password: "${REDIS_PASSWORD}"
|
||||
|
||||
logger:
|
||||
dev: false
|
||||
path: "/var/log/myapi/app.log"
|
||||
```
|
||||
|
||||
### Development Setup
|
||||
|
||||
```bash
|
||||
# Use environment variables for development
|
||||
export RESOLVESPEC_LOGGER_DEV=true
|
||||
export RESOLVESPEC_TRACING_ENABLED=false
|
||||
export RESOLVESPEC_CACHE_PROVIDER=memory
|
||||
```
|
||||
|
||||
### Testing Setup
|
||||
|
||||
```go
|
||||
// Override config for tests
|
||||
mgr := config.NewManager()
|
||||
mgr.Set("cache.provider", "memory")
|
||||
mgr.Set("database.url", testDBURL)
|
||||
|
||||
cfg, _ := mgr.GetConfig()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use config files for base configuration** - Define your standard settings
|
||||
2. **Use environment variables for secrets** - Never commit passwords/tokens
|
||||
3. **Use environment variables for deployment-specific values** - Different per environment
|
||||
4. **Keep defaults sensible** - Application should work with minimal configuration
|
||||
5. **Document your configuration** - Comment your config.yaml files
|
||||
|
||||
## Integration with ResolveSpec Components
|
||||
|
||||
The configuration system integrates seamlessly with ResolveSpec components:
|
||||
|
||||
```go
|
||||
cfg, _ := config.NewManager().Load().GetConfig()
|
||||
|
||||
// Server
|
||||
srv := server.NewGracefulServer(server.Config{
|
||||
Addr: cfg.Server.Addr,
|
||||
ShutdownTimeout: cfg.Server.ShutdownTimeout,
|
||||
// ... other fields
|
||||
})
|
||||
|
||||
// Tracing
|
||||
if cfg.Tracing.Enabled {
|
||||
tracer := tracing.Init(tracing.Config{
|
||||
ServiceName: cfg.Tracing.ServiceName,
|
||||
ServiceVersion: cfg.Tracing.ServiceVersion,
|
||||
Endpoint: cfg.Tracing.Endpoint,
|
||||
})
|
||||
defer tracer.Shutdown(context.Background())
|
||||
}
|
||||
|
||||
// Cache
|
||||
var cacheProvider cache.Provider
|
||||
switch cfg.Cache.Provider {
|
||||
case "redis":
|
||||
cacheProvider = cache.NewRedisProvider(cfg.Cache.Redis.Host, cfg.Cache.Redis.Port, ...)
|
||||
case "memcache":
|
||||
cacheProvider = cache.NewMemcacheProvider(cfg.Cache.Memcache.Servers, ...)
|
||||
default:
|
||||
cacheProvider = cache.NewMemoryProvider()
|
||||
}
|
||||
|
||||
// Logger
|
||||
logger.Init(cfg.Logger.Dev)
|
||||
if cfg.Logger.Path != "" {
|
||||
logger.UpdateLoggerPath(cfg.Logger.Path, cfg.Logger.Dev)
|
||||
}
|
||||
```
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
// Config represents the complete application configuration
|
||||
type Config struct {
|
||||
Servers ServersConfig `mapstructure:"servers"`
|
||||
Tracing TracingConfig `mapstructure:"tracing"`
|
||||
Cache CacheConfig `mapstructure:"cache"`
|
||||
Logger LoggerConfig `mapstructure:"logger"`
|
||||
ErrorTracking ErrorTrackingConfig `mapstructure:"error_tracking"`
|
||||
Middleware MiddlewareConfig `mapstructure:"middleware"`
|
||||
CORS CORSConfig `mapstructure:"cors"`
|
||||
EventBroker EventBrokerConfig `mapstructure:"event_broker"`
|
||||
DBManager DBManagerConfig `mapstructure:"dbmanager"`
|
||||
Paths PathsConfig `mapstructure:"paths"`
|
||||
Extensions map[string]interface{} `mapstructure:"extensions"`
|
||||
}
|
||||
|
||||
// ServersConfig contains configuration for the server manager
|
||||
type ServersConfig struct {
|
||||
// DefaultServer is the name of the default server to use
|
||||
DefaultServer string `mapstructure:"default_server"`
|
||||
|
||||
// Instances is a map of server name to server configuration
|
||||
Instances map[string]ServerInstanceConfig `mapstructure:"instances"`
|
||||
|
||||
// Global timeout defaults (can be overridden per instance)
|
||||
ShutdownTimeout time.Duration `mapstructure:"shutdown_timeout"`
|
||||
DrainTimeout time.Duration `mapstructure:"drain_timeout"`
|
||||
ReadTimeout time.Duration `mapstructure:"read_timeout"`
|
||||
WriteTimeout time.Duration `mapstructure:"write_timeout"`
|
||||
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
|
||||
}
|
||||
|
||||
// ServerInstanceConfig defines configuration for a single server instance
|
||||
type ServerInstanceConfig struct {
|
||||
// Name is the unique name of this server instance
|
||||
Name string `mapstructure:"name"`
|
||||
|
||||
// Host is the host to bind to (e.g., "localhost", "0.0.0.0", "")
|
||||
Host string `mapstructure:"host"`
|
||||
|
||||
// Port is the port number to listen on
|
||||
Port int `mapstructure:"port"`
|
||||
|
||||
// Description is a human-readable description of this server
|
||||
Description string `mapstructure:"description"`
|
||||
|
||||
// GZIP enables GZIP compression middleware
|
||||
GZIP bool `mapstructure:"gzip"`
|
||||
|
||||
// TLS/HTTPS configuration options (mutually exclusive)
|
||||
// Option 1: Provide certificate and key files directly
|
||||
SSLCert string `mapstructure:"ssl_cert"`
|
||||
SSLKey string `mapstructure:"ssl_key"`
|
||||
|
||||
// Option 2: Use self-signed certificate (for development/testing)
|
||||
SelfSignedSSL bool `mapstructure:"self_signed_ssl"`
|
||||
|
||||
// Option 3: Use Let's Encrypt / AutoTLS
|
||||
AutoTLS bool `mapstructure:"auto_tls"`
|
||||
AutoTLSDomains []string `mapstructure:"auto_tls_domains"`
|
||||
AutoTLSCacheDir string `mapstructure:"auto_tls_cache_dir"`
|
||||
AutoTLSEmail string `mapstructure:"auto_tls_email"`
|
||||
|
||||
// Timeout configurations (overrides global defaults)
|
||||
ShutdownTimeout *time.Duration `mapstructure:"shutdown_timeout"`
|
||||
DrainTimeout *time.Duration `mapstructure:"drain_timeout"`
|
||||
ReadTimeout *time.Duration `mapstructure:"read_timeout"`
|
||||
WriteTimeout *time.Duration `mapstructure:"write_timeout"`
|
||||
IdleTimeout *time.Duration `mapstructure:"idle_timeout"`
|
||||
|
||||
// Tags for organization and filtering
|
||||
Tags map[string]string `mapstructure:"tags"`
|
||||
|
||||
// ExternalURLs are additional URLs that this server instance is accessible from (for CORS) for proxy setups
|
||||
ExternalURLs []string `mapstructure:"external_urls"`
|
||||
}
|
||||
|
||||
// TracingConfig holds OpenTelemetry tracing configuration
|
||||
type TracingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
ServiceName string `mapstructure:"service_name"`
|
||||
ServiceVersion string `mapstructure:"service_version"`
|
||||
Endpoint string `mapstructure:"endpoint"`
|
||||
}
|
||||
|
||||
// CacheConfig holds cache provider configuration
|
||||
type CacheConfig struct {
|
||||
Provider string `mapstructure:"provider"` // memory, redis, memcache
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
Memcache MemcacheConfig `mapstructure:"memcache"`
|
||||
}
|
||||
|
||||
// RedisConfig holds Redis-specific configuration
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
// MemcacheConfig holds Memcache-specific configuration
|
||||
type MemcacheConfig struct {
|
||||
Servers []string `mapstructure:"servers"`
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
}
|
||||
|
||||
// LoggerConfig holds logger configuration
|
||||
type LoggerConfig struct {
|
||||
Dev bool `mapstructure:"dev"`
|
||||
Path string `mapstructure:"path"`
|
||||
}
|
||||
|
||||
// MiddlewareConfig holds middleware configuration
|
||||
type MiddlewareConfig struct {
|
||||
RateLimitRPS float64 `mapstructure:"rate_limit_rps"`
|
||||
RateLimitBurst int `mapstructure:"rate_limit_burst"`
|
||||
MaxRequestSize int64 `mapstructure:"max_request_size"`
|
||||
}
|
||||
|
||||
// CORSConfig holds CORS configuration
|
||||
type CORSConfig struct {
|
||||
AllowedOrigins []string `mapstructure:"allowed_origins"`
|
||||
AllowedMethods []string `mapstructure:"allowed_methods"`
|
||||
AllowedHeaders []string `mapstructure:"allowed_headers"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
}
|
||||
|
||||
// ErrorTrackingConfig holds error tracking configuration
|
||||
type ErrorTrackingConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Provider string `mapstructure:"provider"` // sentry, noop
|
||||
DSN string `mapstructure:"dsn"` // Sentry DSN
|
||||
Environment string `mapstructure:"environment"` // e.g., production, staging, development
|
||||
Release string `mapstructure:"release"` // Application version/release
|
||||
Debug bool `mapstructure:"debug"` // Enable debug mode
|
||||
SampleRate float64 `mapstructure:"sample_rate"` // Error sample rate (0.0-1.0)
|
||||
TracesSampleRate float64 `mapstructure:"traces_sample_rate"` // Traces sample rate (0.0-1.0)
|
||||
}
|
||||
|
||||
// EventBrokerConfig contains configuration for the event broker
|
||||
type EventBrokerConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Provider string `mapstructure:"provider"` // memory, redis, nats, database
|
||||
Mode string `mapstructure:"mode"` // sync, async
|
||||
WorkerCount int `mapstructure:"worker_count"`
|
||||
BufferSize int `mapstructure:"buffer_size"`
|
||||
InstanceID string `mapstructure:"instance_id"`
|
||||
Redis EventBrokerRedisConfig `mapstructure:"redis"`
|
||||
NATS EventBrokerNATSConfig `mapstructure:"nats"`
|
||||
Database EventBrokerDatabaseConfig `mapstructure:"database"`
|
||||
RetryPolicy EventBrokerRetryPolicyConfig `mapstructure:"retry_policy"`
|
||||
}
|
||||
|
||||
// EventBrokerRedisConfig contains Redis-specific configuration
|
||||
type EventBrokerRedisConfig struct {
|
||||
StreamName string `mapstructure:"stream_name"`
|
||||
ConsumerGroup string `mapstructure:"consumer_group"`
|
||||
MaxLen int64 `mapstructure:"max_len"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
// EventBrokerNATSConfig contains NATS-specific configuration
|
||||
type EventBrokerNATSConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
StreamName string `mapstructure:"stream_name"`
|
||||
Subjects []string `mapstructure:"subjects"`
|
||||
Storage string `mapstructure:"storage"` // file, memory
|
||||
MaxAge time.Duration `mapstructure:"max_age"`
|
||||
}
|
||||
|
||||
// EventBrokerDatabaseConfig contains database provider configuration
|
||||
type EventBrokerDatabaseConfig struct {
|
||||
TableName string `mapstructure:"table_name"`
|
||||
Channel string `mapstructure:"channel"` // PostgreSQL NOTIFY channel name
|
||||
PollInterval time.Duration `mapstructure:"poll_interval"`
|
||||
}
|
||||
|
||||
// EventBrokerRetryPolicyConfig contains retry policy configuration
|
||||
type EventBrokerRetryPolicyConfig struct {
|
||||
MaxRetries int `mapstructure:"max_retries"`
|
||||
InitialDelay time.Duration `mapstructure:"initial_delay"`
|
||||
MaxDelay time.Duration `mapstructure:"max_delay"`
|
||||
BackoffFactor float64 `mapstructure:"backoff_factor"`
|
||||
}
|
||||
|
||||
// PathsConfig contains configuration for named file system paths
|
||||
// This is a map of path name to file system path
|
||||
// Example: "data_dir": "/var/lib/myapp/data"
|
||||
type PathsConfig map[string]string
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DBManagerConfig contains configuration for the database connection manager
|
||||
type DBManagerConfig struct {
|
||||
// DefaultConnection is the name of the default connection to use
|
||||
DefaultConnection string `mapstructure:"default_connection"`
|
||||
|
||||
// Connections is a map of connection name to connection configuration
|
||||
Connections map[string]DBConnectionConfig `mapstructure:"connections"`
|
||||
|
||||
// Global connection pool defaults
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"`
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||||
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
|
||||
ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"`
|
||||
|
||||
// Retry policy
|
||||
RetryAttempts int `mapstructure:"retry_attempts"`
|
||||
RetryDelay time.Duration `mapstructure:"retry_delay"`
|
||||
RetryMaxDelay time.Duration `mapstructure:"retry_max_delay"`
|
||||
|
||||
// Health checks
|
||||
HealthCheckInterval time.Duration `mapstructure:"health_check_interval"`
|
||||
EnableAutoReconnect bool `mapstructure:"enable_auto_reconnect"`
|
||||
}
|
||||
|
||||
// DBConnectionConfig defines configuration for a single database connection
|
||||
type DBConnectionConfig struct {
|
||||
// Name is the unique name of this connection
|
||||
Name string `mapstructure:"name"`
|
||||
|
||||
// Type is the database type (postgres, sqlite, mssql, mongodb)
|
||||
Type string `mapstructure:"type"`
|
||||
|
||||
// DSN is the complete Data Source Name / connection string
|
||||
// If provided, this takes precedence over individual connection parameters
|
||||
DSN string `mapstructure:"dsn"`
|
||||
|
||||
// Connection parameters (used if DSN is not provided)
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
Database string `mapstructure:"database"`
|
||||
|
||||
// PostgreSQL/MSSQL specific
|
||||
SSLMode string `mapstructure:"sslmode"` // disable, require, verify-ca, verify-full
|
||||
Schema string `mapstructure:"schema"` // Default schema
|
||||
|
||||
// SQLite specific
|
||||
FilePath string `mapstructure:"filepath"`
|
||||
|
||||
// MongoDB specific
|
||||
AuthSource string `mapstructure:"auth_source"`
|
||||
ReplicaSet string `mapstructure:"replica_set"`
|
||||
ReadPreference string `mapstructure:"read_preference"` // primary, secondary, etc.
|
||||
|
||||
// Connection pool settings (overrides global defaults)
|
||||
MaxOpenConns *int `mapstructure:"max_open_conns"`
|
||||
MaxIdleConns *int `mapstructure:"max_idle_conns"`
|
||||
ConnMaxLifetime *time.Duration `mapstructure:"conn_max_lifetime"`
|
||||
ConnMaxIdleTime *time.Duration `mapstructure:"conn_max_idle_time"`
|
||||
|
||||
// Timeouts
|
||||
ConnectTimeout time.Duration `mapstructure:"connect_timeout"`
|
||||
QueryTimeout time.Duration `mapstructure:"query_timeout"`
|
||||
|
||||
// Features
|
||||
EnableTracing bool `mapstructure:"enable_tracing"`
|
||||
EnableMetrics bool `mapstructure:"enable_metrics"`
|
||||
EnableLogging bool `mapstructure:"enable_logging"`
|
||||
|
||||
// DefaultORM specifies which ORM to use for the Database() method
|
||||
// Options: "bun", "gorm", "native"
|
||||
DefaultORM string `mapstructure:"default_orm"`
|
||||
|
||||
// Tags for organization and filtering
|
||||
Tags map[string]string `mapstructure:"tags"`
|
||||
}
|
||||
|
||||
// ToManagerConfig converts config.DBManagerConfig to dbmanager.ManagerConfig
|
||||
// This is used to avoid circular dependencies
|
||||
func (c *DBManagerConfig) ToManagerConfig() interface{} {
|
||||
// This will be implemented in the dbmanager package
|
||||
// to convert from config types to dbmanager types
|
||||
return c
|
||||
}
|
||||
|
||||
// PopulateFromDSN parses a DSN and populates the connection fields
|
||||
func (cc *DBConnectionConfig) PopulateFromDSN() error {
|
||||
if cc.DSN == "" {
|
||||
return nil // Nothing to populate
|
||||
}
|
||||
|
||||
switch cc.Type {
|
||||
case "postgres":
|
||||
return cc.populatePostgresDSN()
|
||||
case "mongodb":
|
||||
return cc.populateMongoDSN()
|
||||
case "mssql":
|
||||
return cc.populateMSSQLDSN()
|
||||
case "sqlite":
|
||||
return cc.populateSQLiteDSN()
|
||||
default:
|
||||
return fmt.Errorf("cannot parse DSN for unsupported database type: %s", cc.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// populatePostgresDSN parses PostgreSQL DSN format
|
||||
// Example: host=localhost port=5432 user=postgres password=secret dbname=mydb sslmode=disable
|
||||
func (cc *DBConnectionConfig) populatePostgresDSN() error {
|
||||
parts := strings.Fields(cc.DSN)
|
||||
for _, part := range parts {
|
||||
kv := strings.SplitN(part, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
key, value := kv[0], kv[1]
|
||||
|
||||
switch key {
|
||||
case "host":
|
||||
cc.Host = value
|
||||
case "port":
|
||||
port, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port in DSN: %w", err)
|
||||
}
|
||||
cc.Port = port
|
||||
case "user":
|
||||
cc.User = value
|
||||
case "password":
|
||||
cc.Password = value
|
||||
case "dbname":
|
||||
cc.Database = value
|
||||
case "sslmode":
|
||||
cc.SSLMode = value
|
||||
case "search_path":
|
||||
cc.Schema = value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateMongoDSN parses MongoDB DSN format
|
||||
// Example: mongodb://user:password@host:port/database?authSource=admin&replicaSet=rs0
|
||||
func (cc *DBConnectionConfig) populateMongoDSN() error {
|
||||
u, err := url.Parse(cc.DSN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid MongoDB DSN: %w", err)
|
||||
}
|
||||
|
||||
// Extract user and password
|
||||
if u.User != nil {
|
||||
cc.User = u.User.Username()
|
||||
if password, ok := u.User.Password(); ok {
|
||||
cc.Password = password
|
||||
}
|
||||
}
|
||||
|
||||
// Extract host and port
|
||||
if u.Host != "" {
|
||||
host := u.Host
|
||||
if strings.Contains(host, ":") {
|
||||
hostPort := strings.SplitN(host, ":", 2)
|
||||
cc.Host = hostPort[0]
|
||||
if port, err := strconv.Atoi(hostPort[1]); err == nil {
|
||||
cc.Port = port
|
||||
}
|
||||
} else {
|
||||
cc.Host = host
|
||||
}
|
||||
}
|
||||
|
||||
// Extract database
|
||||
if u.Path != "" {
|
||||
cc.Database = strings.TrimPrefix(u.Path, "/")
|
||||
}
|
||||
|
||||
// Extract query parameters
|
||||
params := u.Query()
|
||||
if authSource := params.Get("authSource"); authSource != "" {
|
||||
cc.AuthSource = authSource
|
||||
}
|
||||
if replicaSet := params.Get("replicaSet"); replicaSet != "" {
|
||||
cc.ReplicaSet = replicaSet
|
||||
}
|
||||
if readPref := params.Get("readPreference"); readPref != "" {
|
||||
cc.ReadPreference = readPref
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateMSSQLDSN parses MSSQL DSN format
|
||||
// Example: sqlserver://username:password@host:port?database=dbname&schema=dbo
|
||||
func (cc *DBConnectionConfig) populateMSSQLDSN() error {
|
||||
u, err := url.Parse(cc.DSN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid MSSQL DSN: %w", err)
|
||||
}
|
||||
|
||||
// Extract user and password
|
||||
if u.User != nil {
|
||||
cc.User = u.User.Username()
|
||||
if password, ok := u.User.Password(); ok {
|
||||
cc.Password = password
|
||||
}
|
||||
}
|
||||
|
||||
// Extract host and port
|
||||
if u.Host != "" {
|
||||
host := u.Host
|
||||
if strings.Contains(host, ":") {
|
||||
hostPort := strings.SplitN(host, ":", 2)
|
||||
cc.Host = hostPort[0]
|
||||
if port, err := strconv.Atoi(hostPort[1]); err == nil {
|
||||
cc.Port = port
|
||||
}
|
||||
} else {
|
||||
cc.Host = host
|
||||
}
|
||||
}
|
||||
|
||||
// Extract query parameters
|
||||
params := u.Query()
|
||||
if database := params.Get("database"); database != "" {
|
||||
cc.Database = database
|
||||
}
|
||||
if schema := params.Get("schema"); schema != "" {
|
||||
cc.Schema = schema
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateSQLiteDSN parses SQLite DSN format
|
||||
// Example: /path/to/database.db or :memory:
|
||||
func (cc *DBConnectionConfig) populateSQLiteDSN() error {
|
||||
cc.FilePath = cc.DSN
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates the DBManager configuration
|
||||
func (c *DBManagerConfig) Validate() error {
|
||||
if len(c.Connections) == 0 {
|
||||
return fmt.Errorf("at least one connection must be configured")
|
||||
}
|
||||
|
||||
if c.DefaultConnection != "" {
|
||||
if _, ok := c.Connections[c.DefaultConnection]; !ok {
|
||||
return fmt.Errorf("default connection '%s' not found in connections", c.DefaultConnection)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Manager handles configuration loading from multiple sources
|
||||
type Manager struct {
|
||||
v *viper.Viper
|
||||
}
|
||||
|
||||
var configInstance *Manager
|
||||
|
||||
// GetConfigManager returns a singleton configuration manager instance
|
||||
func GetConfigManager() *Manager {
|
||||
if configInstance == nil {
|
||||
configInstance = NewManager()
|
||||
}
|
||||
return configInstance
|
||||
}
|
||||
|
||||
// NewManager creates a new configuration manager with defaults
|
||||
func NewManager() *Manager {
|
||||
v := viper.New()
|
||||
|
||||
// Set configuration file settings
|
||||
v.SetConfigName("config")
|
||||
v.SetConfigType("yaml")
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath("./config")
|
||||
v.AddConfigPath("/etc/resolvespec")
|
||||
v.AddConfigPath("$HOME/.resolvespec")
|
||||
|
||||
// Enable environment variable support
|
||||
v.SetEnvPrefix("RESOLVESPEC")
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.AutomaticEnv()
|
||||
|
||||
// Set default values
|
||||
setDefaults(v)
|
||||
|
||||
configInstance = &Manager{v: v}
|
||||
return configInstance
|
||||
}
|
||||
|
||||
// NewManagerWithOptions creates a new configuration manager with custom options
|
||||
func NewManagerWithOptions(opts ...Option) *Manager {
|
||||
m := NewManager()
|
||||
for _, opt := range opts {
|
||||
opt(m)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Option is a functional option for configuring the Manager
|
||||
type Option func(*Manager)
|
||||
|
||||
// WithConfigFile sets a specific config file path
|
||||
func WithConfigFile(path string) Option {
|
||||
return func(m *Manager) {
|
||||
m.v.SetConfigFile(path)
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfigName sets the config file name (without extension)
|
||||
func WithConfigName(name string) Option {
|
||||
return func(m *Manager) {
|
||||
m.v.SetConfigName(name)
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfigPath adds a path to search for config files
|
||||
func WithConfigPath(path string) Option {
|
||||
return func(m *Manager) {
|
||||
m.v.AddConfigPath(path)
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnvPrefix sets the environment variable prefix
|
||||
func WithEnvPrefix(prefix string) Option {
|
||||
return func(m *Manager) {
|
||||
m.v.SetEnvPrefix(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// Load attempts to load configuration from file and environment
|
||||
func (m *Manager) Load() error {
|
||||
// Try to read config file (not an error if it doesn't exist)
|
||||
if err := m.v.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
return fmt.Errorf("error reading config file: %w", err)
|
||||
}
|
||||
// Config file not found; will rely on defaults and env vars
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfig returns the complete configuration
|
||||
func (m *Manager) GetConfig() (*Config, error) {
|
||||
var cfg Config
|
||||
if err := m.v.Unmarshal(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// SetConfig sets the complete configuration
|
||||
func (m *Manager) SetConfig(cfg *Config) error {
|
||||
configMap := make(map[string]interface{})
|
||||
|
||||
// Marshal the config to a map structure that viper can use
|
||||
if err := m.v.Unmarshal(&configMap); err != nil {
|
||||
return fmt.Errorf("failed to prepare config map: %w", err)
|
||||
}
|
||||
|
||||
// Use viper's merge to apply the config
|
||||
m.v.Set("servers", cfg.Servers)
|
||||
m.v.Set("tracing", cfg.Tracing)
|
||||
m.v.Set("cache", cfg.Cache)
|
||||
m.v.Set("logger", cfg.Logger)
|
||||
m.v.Set("error_tracking", cfg.ErrorTracking)
|
||||
m.v.Set("middleware", cfg.Middleware)
|
||||
m.v.Set("cors", cfg.CORS)
|
||||
m.v.Set("event_broker", cfg.EventBroker)
|
||||
m.v.Set("dbmanager", cfg.DBManager)
|
||||
m.v.Set("paths", cfg.Paths)
|
||||
m.v.Set("extensions", cfg.Extensions)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns a configuration value by key
|
||||
func (m *Manager) Get(key string) interface{} {
|
||||
return m.v.Get(key)
|
||||
}
|
||||
|
||||
// GetString returns a string configuration value
|
||||
func (m *Manager) GetString(key string) string {
|
||||
return m.v.GetString(key)
|
||||
}
|
||||
|
||||
// GetInt returns an int configuration value
|
||||
func (m *Manager) GetInt(key string) int {
|
||||
return m.v.GetInt(key)
|
||||
}
|
||||
|
||||
// GetBool returns a bool configuration value
|
||||
func (m *Manager) GetBool(key string) bool {
|
||||
return m.v.GetBool(key)
|
||||
}
|
||||
|
||||
// Set sets a configuration value
|
||||
func (m *Manager) Set(key string, value interface{}) {
|
||||
m.v.Set(key, value)
|
||||
}
|
||||
|
||||
// SaveConfig writes the current configuration to the specified path
|
||||
func (m *Manager) SaveConfig(path string) error {
|
||||
if err := m.v.WriteConfigAs(path); err != nil {
|
||||
return fmt.Errorf("failed to save config to %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setDefaults sets default configuration values
|
||||
func setDefaults(v *viper.Viper) {
|
||||
// Server defaults - new structure
|
||||
v.SetDefault("servers.default_server", "default")
|
||||
|
||||
// Global server timeout defaults
|
||||
v.SetDefault("servers.shutdown_timeout", "30s")
|
||||
v.SetDefault("servers.drain_timeout", "25s")
|
||||
v.SetDefault("servers.read_timeout", "10s")
|
||||
v.SetDefault("servers.write_timeout", "10s")
|
||||
v.SetDefault("servers.idle_timeout", "120s")
|
||||
|
||||
// Default server instance
|
||||
v.SetDefault("servers.instances.default.name", "default")
|
||||
v.SetDefault("servers.instances.default.host", "")
|
||||
v.SetDefault("servers.instances.default.port", 8080)
|
||||
v.SetDefault("servers.instances.default.description", "Default HTTP server")
|
||||
v.SetDefault("servers.instances.default.gzip", false)
|
||||
|
||||
// Tracing defaults
|
||||
v.SetDefault("tracing.enabled", false)
|
||||
v.SetDefault("tracing.service_name", "resolvespec")
|
||||
v.SetDefault("tracing.service_version", "1.0.0")
|
||||
v.SetDefault("tracing.endpoint", "")
|
||||
|
||||
// Cache defaults
|
||||
v.SetDefault("cache.provider", "memory")
|
||||
v.SetDefault("cache.redis.host", "localhost")
|
||||
v.SetDefault("cache.redis.port", 6379)
|
||||
v.SetDefault("cache.redis.password", "")
|
||||
v.SetDefault("cache.redis.db", 0)
|
||||
v.SetDefault("cache.memcache.servers", []string{"localhost:11211"})
|
||||
v.SetDefault("cache.memcache.max_idle_conns", 10)
|
||||
v.SetDefault("cache.memcache.timeout", "100ms")
|
||||
|
||||
// Logger defaults
|
||||
v.SetDefault("logger.dev", false)
|
||||
v.SetDefault("logger.path", "")
|
||||
|
||||
// Middleware defaults
|
||||
v.SetDefault("middleware.rate_limit_rps", 100.0)
|
||||
v.SetDefault("middleware.rate_limit_burst", 200)
|
||||
v.SetDefault("middleware.max_request_size", 10485760) // 10MB
|
||||
|
||||
// CORS defaults
|
||||
v.SetDefault("cors.allowed_origins", []string{"*"})
|
||||
v.SetDefault("cors.allowed_methods", []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"})
|
||||
v.SetDefault("cors.allowed_headers", []string{"*"})
|
||||
v.SetDefault("cors.max_age", 3600)
|
||||
|
||||
// Database defaults
|
||||
v.SetDefault("database.url", "")
|
||||
|
||||
// Database Manager defaults
|
||||
v.SetDefault("dbmanager.default_connection", "default")
|
||||
v.SetDefault("dbmanager.max_open_conns", 25)
|
||||
v.SetDefault("dbmanager.max_idle_conns", 5)
|
||||
v.SetDefault("dbmanager.conn_max_lifetime", "30m")
|
||||
v.SetDefault("dbmanager.conn_max_idle_time", "5m")
|
||||
v.SetDefault("dbmanager.retry_attempts", 3)
|
||||
v.SetDefault("dbmanager.retry_delay", "1s")
|
||||
v.SetDefault("dbmanager.retry_max_delay", "10s")
|
||||
v.SetDefault("dbmanager.health_check_interval", "30s")
|
||||
v.SetDefault("dbmanager.enable_auto_reconnect", true)
|
||||
|
||||
// Default PostgreSQL connection
|
||||
v.SetDefault("dbmanager.connections.default.name", "default")
|
||||
v.SetDefault("dbmanager.connections.default.type", "postgres")
|
||||
v.SetDefault("dbmanager.connections.default.host", "localhost")
|
||||
v.SetDefault("dbmanager.connections.default.port", 5432)
|
||||
v.SetDefault("dbmanager.connections.default.user", "postgres")
|
||||
v.SetDefault("dbmanager.connections.default.password", "")
|
||||
v.SetDefault("dbmanager.connections.default.database", "resolvespec")
|
||||
v.SetDefault("dbmanager.connections.default.sslmode", "disable")
|
||||
v.SetDefault("dbmanager.connections.default.connect_timeout", "10s")
|
||||
v.SetDefault("dbmanager.connections.default.query_timeout", "30s")
|
||||
v.SetDefault("dbmanager.connections.default.enable_tracing", false)
|
||||
v.SetDefault("dbmanager.connections.default.enable_metrics", false)
|
||||
v.SetDefault("dbmanager.connections.default.enable_logging", false)
|
||||
v.SetDefault("dbmanager.connections.default.default_orm", "bun")
|
||||
|
||||
// Event Broker defaults
|
||||
v.SetDefault("event_broker.enabled", false)
|
||||
v.SetDefault("event_broker.provider", "memory")
|
||||
v.SetDefault("event_broker.mode", "async")
|
||||
v.SetDefault("event_broker.worker_count", 10)
|
||||
v.SetDefault("event_broker.buffer_size", 1000)
|
||||
v.SetDefault("event_broker.instance_id", "")
|
||||
|
||||
// Event Broker - Redis defaults
|
||||
v.SetDefault("event_broker.redis.stream_name", "resolvespec:events")
|
||||
v.SetDefault("event_broker.redis.consumer_group", "resolvespec-workers")
|
||||
v.SetDefault("event_broker.redis.max_len", 10000)
|
||||
v.SetDefault("event_broker.redis.host", "localhost")
|
||||
v.SetDefault("event_broker.redis.port", 6379)
|
||||
v.SetDefault("event_broker.redis.password", "")
|
||||
v.SetDefault("event_broker.redis.db", 0)
|
||||
|
||||
// Event Broker - NATS defaults
|
||||
v.SetDefault("event_broker.nats.url", "nats://localhost:4222")
|
||||
v.SetDefault("event_broker.nats.stream_name", "RESOLVESPEC_EVENTS")
|
||||
v.SetDefault("event_broker.nats.subjects", []string{"events.>"})
|
||||
v.SetDefault("event_broker.nats.storage", "file")
|
||||
v.SetDefault("event_broker.nats.max_age", "24h")
|
||||
|
||||
// Event Broker - Database defaults
|
||||
v.SetDefault("event_broker.database.table_name", "events")
|
||||
v.SetDefault("event_broker.database.channel", "resolvespec_events")
|
||||
v.SetDefault("event_broker.database.poll_interval", "1s")
|
||||
|
||||
// Event Broker - Retry Policy defaults
|
||||
v.SetDefault("event_broker.retry_policy.max_retries", 3)
|
||||
v.SetDefault("event_broker.retry_policy.initial_delay", "1s")
|
||||
v.SetDefault("event_broker.retry_policy.max_delay", "30s")
|
||||
v.SetDefault("event_broker.retry_policy.backoff_factor", 2.0)
|
||||
|
||||
// Paths defaults (common directory paths)
|
||||
v.SetDefault("paths.data_dir", "./data")
|
||||
v.SetDefault("paths.config_dir", "./config")
|
||||
v.SetDefault("paths.logs_dir", "./logs")
|
||||
v.SetDefault("paths.temp_dir", "./tmp")
|
||||
|
||||
// Extensions defaults (empty map)
|
||||
v.SetDefault("extensions", map[string]interface{}{})
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Get retrieves a path by name
|
||||
func (pc PathsConfig) Get(name string) (string, error) {
|
||||
if pc == nil {
|
||||
return "", fmt.Errorf("paths not initialized")
|
||||
}
|
||||
|
||||
path, ok := pc[name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("path '%s' not found", name)
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// GetOrDefault retrieves a path by name, returning defaultPath if not found
|
||||
func (pc PathsConfig) GetOrDefault(name, defaultPath string) string {
|
||||
if pc == nil {
|
||||
return defaultPath
|
||||
}
|
||||
|
||||
path, ok := pc[name]
|
||||
if !ok {
|
||||
return defaultPath
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// Set sets a path by name
|
||||
func (pc PathsConfig) Set(name, path string) {
|
||||
pc[name] = path
|
||||
}
|
||||
|
||||
// Has checks if a path exists by name
|
||||
func (pc PathsConfig) Has(name string) bool {
|
||||
if pc == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := pc[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// EnsureDir ensures a directory exists at the specified path name
|
||||
// Creates the directory if it doesn't exist with the given permissions
|
||||
func (pc PathsConfig) EnsureDir(name string, perm os.FileMode) error {
|
||||
path, err := pc.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if directory exists
|
||||
info, err := os.Stat(path)
|
||||
if err == nil {
|
||||
// Path exists, check if it's a directory
|
||||
if !info.IsDir() {
|
||||
return fmt.Errorf("path '%s' exists but is not a directory: %s", name, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Directory doesn't exist, create it
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(path, perm); err != nil {
|
||||
return fmt.Errorf("failed to create directory for '%s' at %s: %w", name, path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to stat path '%s' at %s: %w", name, path, err)
|
||||
}
|
||||
|
||||
// AbsPath returns the absolute path for a named path
|
||||
func (pc PathsConfig) AbsPath(name string) (string, error) {
|
||||
path, err := pc.Get(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get absolute path for '%s': %w", name, err)
|
||||
}
|
||||
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
// Join joins path segments with a named base path
|
||||
func (pc PathsConfig) Join(name string, elem ...string) (string, error) {
|
||||
base, err := pc.Get(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parts := append([]string{base}, elem...)
|
||||
return filepath.Join(parts...), nil
|
||||
}
|
||||
|
||||
// List returns all configured path names
|
||||
func (pc PathsConfig) List() []string {
|
||||
if pc == nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(pc))
|
||||
for name := range pc {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ApplyGlobalDefaults applies global server defaults to this instance
|
||||
// Called for instances that don't specify their own timeout values
|
||||
func (sic *ServerInstanceConfig) ApplyGlobalDefaults(globals ServersConfig) {
|
||||
if sic.ShutdownTimeout == nil && globals.ShutdownTimeout > 0 {
|
||||
t := globals.ShutdownTimeout
|
||||
sic.ShutdownTimeout = &t
|
||||
}
|
||||
if sic.DrainTimeout == nil && globals.DrainTimeout > 0 {
|
||||
t := globals.DrainTimeout
|
||||
sic.DrainTimeout = &t
|
||||
}
|
||||
if sic.ReadTimeout == nil && globals.ReadTimeout > 0 {
|
||||
t := globals.ReadTimeout
|
||||
sic.ReadTimeout = &t
|
||||
}
|
||||
if sic.WriteTimeout == nil && globals.WriteTimeout > 0 {
|
||||
t := globals.WriteTimeout
|
||||
sic.WriteTimeout = &t
|
||||
}
|
||||
if sic.IdleTimeout == nil && globals.IdleTimeout > 0 {
|
||||
t := globals.IdleTimeout
|
||||
sic.IdleTimeout = &t
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the ServerInstanceConfig
|
||||
func (sic *ServerInstanceConfig) Validate() error {
|
||||
if sic.Name == "" {
|
||||
return fmt.Errorf("server instance name cannot be empty")
|
||||
}
|
||||
if sic.Port <= 0 || sic.Port > 65535 {
|
||||
return fmt.Errorf("invalid port: %d (must be 1-65535)", sic.Port)
|
||||
}
|
||||
|
||||
// Validate TLS options are mutually exclusive
|
||||
tlsCount := 0
|
||||
if sic.SSLCert != "" || sic.SSLKey != "" {
|
||||
tlsCount++
|
||||
}
|
||||
if sic.SelfSignedSSL {
|
||||
tlsCount++
|
||||
}
|
||||
if sic.AutoTLS {
|
||||
tlsCount++
|
||||
}
|
||||
if tlsCount > 1 {
|
||||
return fmt.Errorf("server '%s': only one TLS option can be enabled", sic.Name)
|
||||
}
|
||||
|
||||
// If using certificate files, both must be provided
|
||||
if (sic.SSLCert != "" && sic.SSLKey == "") || (sic.SSLCert == "" && sic.SSLKey != "") {
|
||||
return fmt.Errorf("server '%s': both ssl_cert and ssl_key must be provided", sic.Name)
|
||||
}
|
||||
|
||||
// If using AutoTLS, domains must be specified
|
||||
if sic.AutoTLS && len(sic.AutoTLSDomains) == 0 {
|
||||
return fmt.Errorf("server '%s': auto_tls_domains must be specified when auto_tls is enabled", sic.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates the ServersConfig
|
||||
func (sc *ServersConfig) Validate() error {
|
||||
if len(sc.Instances) == 0 {
|
||||
return fmt.Errorf("at least one server instance must be configured")
|
||||
}
|
||||
|
||||
if sc.DefaultServer != "" {
|
||||
if _, ok := sc.Instances[sc.DefaultServer]; !ok {
|
||||
return fmt.Errorf("default server '%s' not found in instances", sc.DefaultServer)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each instance
|
||||
for name := range sc.Instances {
|
||||
instance := sc.Instances[name]
|
||||
if instance.Name != name {
|
||||
return fmt.Errorf("server instance name mismatch: key='%s', name='%s'", name, instance.Name)
|
||||
}
|
||||
if err := instance.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDefault returns the default server instance configuration
|
||||
func (sc *ServersConfig) GetDefault() (*ServerInstanceConfig, error) {
|
||||
if sc.DefaultServer == "" {
|
||||
return nil, fmt.Errorf("no default server configured")
|
||||
}
|
||||
|
||||
instance, ok := sc.Instances[sc.DefaultServer]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("default server '%s' not found", sc.DefaultServer)
|
||||
}
|
||||
|
||||
return &instance, nil
|
||||
}
|
||||
|
||||
// GetIPs - GetIP for pc
|
||||
func GetIPs() (hostname string, ipList string, ipNetList []net.IP) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println("Recovered in GetIPs", err)
|
||||
}
|
||||
}()
|
||||
hostname, _ = os.Hostname()
|
||||
ipaddrlist := make([]net.IP, 0)
|
||||
iplist := ""
|
||||
addrs, err := net.LookupIP(hostname)
|
||||
if err != nil {
|
||||
return hostname, iplist, ipaddrlist
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
// cfg.LogInfo("\nFound IP Host Address: %s", a)
|
||||
if strings.Contains(a.String(), "127.0.0.1") {
|
||||
continue
|
||||
}
|
||||
iplist = fmt.Sprintf("%s,%s", iplist, a)
|
||||
ipaddrlist = append(ipaddrlist, a)
|
||||
}
|
||||
if iplist == "" {
|
||||
iff, _ := net.InterfaceAddrs()
|
||||
for _, a := range iff {
|
||||
// cfg.LogInfo("\nFound IP Address: %s", a)
|
||||
if strings.Contains(a.String(), "127.0.0.1") {
|
||||
continue
|
||||
}
|
||||
iplist = fmt.Sprintf("%s,%s", iplist, a)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
iplist = strings.TrimLeft(iplist, ",")
|
||||
return hostname, iplist, ipaddrlist
|
||||
}
|
||||
Reference in New Issue
Block a user